From 7b10e011e63c18ff57dc939290fb623d259358fb Mon Sep 17 00:00:00 2001 From: David Contreras Date: Mon, 16 Sep 2024 10:50:47 -0600 Subject: [PATCH 01/19] Faster deepMap recurse --- src/function/matrix/forEach.js | 4 +-- src/function/matrix/map.js | 4 +-- src/type/matrix/DenseMatrix.js | 6 ++-- src/utils/array.js | 50 ++++++++++++++++++++++++++++------ src/utils/collection.js | 42 ++++++++++++++++++---------- src/utils/optimizeCallback.js | 10 +++---- 6 files changed, 81 insertions(+), 35 deletions(-) diff --git a/src/function/matrix/forEach.js b/src/function/matrix/forEach.js index 3dfa266278..5ceb158815 100644 --- a/src/function/matrix/forEach.js +++ b/src/function/matrix/forEach.js @@ -1,6 +1,6 @@ import { optimizeCallback } from '../../utils/optimizeCallback.js' import { factory } from '../../utils/factory.js' -import { recurse } from '../../utils/array.js' +import { deepForEach } from '../../utils/array.js' const name = 'forEach' const dependencies = ['typed'] @@ -45,5 +45,5 @@ export const createForEach = /* #__PURE__ */ factory(name, dependencies, ({ type * @private */ function _forEach (array, callback) { - recurse(array, [], array, optimizeCallback(callback, array, name)) + deepForEach(array, array, optimizeCallback(callback, array, name)) } diff --git a/src/function/matrix/map.js b/src/function/matrix/map.js index d8ff940431..fb4c94ab82 100644 --- a/src/function/matrix/map.js +++ b/src/function/matrix/map.js @@ -1,5 +1,5 @@ import { optimizeCallback } from '../../utils/optimizeCallback.js' -import { arraySize, broadcastSizes, broadcastTo, get, recurse } from '../../utils/array.js' +import { arraySize, broadcastSizes, broadcastTo, get, deepMap } from '../../utils/array.js' import { factory } from '../../utils/factory.js' const name = 'map' @@ -151,6 +151,6 @@ export const createMap = /* #__PURE__ */ factory(name, dependencies, ({ typed }) * @private */ function _mapArray (array, callback) { - return recurse(array, [], array, optimizeCallback(callback, array, name)) + return deepMap(array, array, optimizeCallback(callback, array, name)) } }) diff --git a/src/type/matrix/DenseMatrix.js b/src/type/matrix/DenseMatrix.js index a05a593042..35663be902 100644 --- a/src/type/matrix/DenseMatrix.js +++ b/src/type/matrix/DenseMatrix.js @@ -1,5 +1,5 @@ import { isArray, isBigNumber, isCollection, isIndex, isMatrix, isNumber, isString, typeOf } from '../../utils/is.js' -import { arraySize, getArrayDataType, processSizesWildcard, reshape, resize, unsqueeze, validate, validateIndex, broadcastTo, get, recurse } from '../../utils/array.js' +import { arraySize, getArrayDataType, processSizesWildcard, reshape, resize, unsqueeze, validate, validateIndex, broadcastTo, get, deepMap, deepForEach } from '../../utils/array.js' import { format } from '../../utils/string.js' import { isInteger } from '../../utils/number.js' import { clone, deepStrictEqual } from '../../utils/object.js' @@ -541,7 +541,7 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies // determine the new datatype when the original matrix has datatype defined // TODO: should be done in matrix constructor instead - const data = recurse(me._data, [], me, fastCallback) + const data = deepMap(me._data, me, fastCallback) const datatype = me._datatype !== undefined ? getArrayDataType(data, typeOf) : undefined @@ -559,7 +559,7 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies // matrix instance const me = this const fastCallback = optimizeCallback(callback, me._data, 'forEach') - recurse(this._data, [], me, fastCallback) + deepForEach(this._data, me, fastCallback) } /** diff --git a/src/utils/array.js b/src/utils/array.js index 4017983361..3894e336f0 100644 --- a/src/utils/array.js +++ b/src/utils/array.js @@ -834,15 +834,47 @@ export function get (array, index) { * @param {Function} callback - Function that produces the element of the new Array, taking three arguments: the value of the element, the index of the element, and the Array being processed. * @returns {*} The new array with each element being the result of the callback function. */ -export function recurse (value, index, array, callback) { - if (Array.isArray(value)) { - return value.map(function (child, i) { - // we create a copy of the index array and append the new index value - return recurse(child, index.concat(i), array, callback) - }) - } else { - // invoke the callback function with the right number of arguments - return callback(value, index, array) +export function deepMap (value, array, callback) { + return recurse(value, [], array, callback) + function recurse (value, index, array, callback) { + if (Array.isArray(value)) { + return value.map(function (child, i) { + // we create a copy of the index array and append the new index value + index.push(i) + const results = recurse(child, index, array, callback) + index.pop() + return results + }) + } else { + // invoke the callback function with the right number of arguments + return callback(value, [...index], array) + } + } +} + +/** + * Recursive function to map a multi-dimensional array. + * + * @param {*} value - The current value being processed in the array. + * @param {Array} index - The index of the current value being processed in the array. + * @param {Array} array - The array being processed. + * @param {Function} callback - Function that produces the element of the new Array, taking three arguments: the value of the element, the index of the element, and the Array being processed. + * @returns {*} The new array with each element being the result of the callback function. + */ +export function deepForEach (value, array, callback) { + recurse(value, [], array, callback) + function recurse (value, index, array, callback) { + if (Array.isArray(value)) { + return value.forEach(function (child, i) { + // we create a copy of the index array and append the new index value + index.push(i) + recurse(child, index, array, callback) + index.pop() + }) + } else { + // invoke the callback function with the right number of arguments + callback(value, [...index], array) + } } } diff --git a/src/utils/collection.js b/src/utils/collection.js index 021d980cba..8c740fc98c 100644 --- a/src/utils/collection.js +++ b/src/utils/collection.js @@ -27,16 +27,15 @@ export function containsCollections (array) { */ export function deepForEach (array, callback) { if (isMatrix(array)) { - array = array.valueOf() + recurse(array.valueOf()) + } else { + recurse(array) } - - for (let i = 0, ii = array.length; i < ii; i++) { - const value = array[i] - - if (Array.isArray(value)) { - deepForEach(value, callback) + function recurse (array) { + if (Array.isArray(array)) { + array.forEach(value => recurse(value)) } else { - callback(value) + callback(array) } } } @@ -54,13 +53,28 @@ export function deepForEach (array, callback) { * @return {Array | Matrix} res */ export function deepMap (array, callback, skipZeros) { - if (array && (typeof array.map === 'function')) { - // TODO: replace array.map with a for loop to improve performance - return array.map(function (x) { - return deepMap(x, callback, skipZeros) - }) + if (skipZeros) { + const callbackSkip = (x) => x === 0 ? 0 : callback(x) + if (isMatrix(array)) { + return array.create(recurse(array.valueOf(), callbackSkip), array.datatype()) + } else { + return recurse(array, callbackSkip) + } } else { - return callback(array) + if (isMatrix(array)) { + return array.create(recurse(array.valueOf(), callback), array.datatype()) + } else { + return recurse(array, callback) + } + } + function recurse (array) { + if (Array.isArray(array)) { + return array.map(function (x) { + return recurse(x) + }) + } else { + return callback(array) + } } } diff --git a/src/utils/optimizeCallback.js b/src/utils/optimizeCallback.js index 4f0166ee30..68657c3ad6 100644 --- a/src/utils/optimizeCallback.js +++ b/src/utils/optimizeCallback.js @@ -15,17 +15,17 @@ export function optimizeCallback (callback, array, name) { const firstIndex = (array.isMatrix ? array.size() : arraySize(array)).map(() => 0) const firstValue = array.isMatrix ? array.get(firstIndex) : get(array, firstIndex) const hasSingleSignature = Object.keys(callback.signatures).length === 1 - const numberOfArguments = _findNumberOfArguments(callback, firstValue, firstIndex, array) + const numberOfArguments = findNumberOfArguments(callback, firstValue, firstIndex, array) const fastCallback = hasSingleSignature ? Object.values(callback.signatures)[0] : callback if (numberOfArguments >= 1 && numberOfArguments <= 3) { - return (...args) => _tryFunctionWithArgs(fastCallback, args.slice(0, numberOfArguments), name, callback.name) + return (...args) => tryFunctionWithArgs(fastCallback, args.slice(0, numberOfArguments), name, callback.name) } - return (...args) => _tryFunctionWithArgs(fastCallback, args, name, callback.name) + return (...args) => tryFunctionWithArgs(fastCallback, args, name, callback.name) } return callback } -function _findNumberOfArguments (callback, value, index, array) { +export function findNumberOfArguments (callback, value, index, array) { const testArgs = [value, index, array] for (let i = 3; i > 0; i--) { const args = testArgs.slice(0, i) @@ -43,7 +43,7 @@ function _findNumberOfArguments (callback, value, index, array) { * @returns {*} Returns the return value of the invoked signature * @throws {TypeError} Throws an error when no matching signature was found */ -function _tryFunctionWithArgs (func, args, mappingFnName, callbackName) { +export function tryFunctionWithArgs (func, args, mappingFnName, callbackName) { try { return func(...args) } catch (err) { From c7d98dff010227cabc7537b2571caf4f1e5dec7c Mon Sep 17 00:00:00 2001 From: David Contreras Date: Mon, 16 Sep 2024 11:18:31 -0600 Subject: [PATCH 02/19] Callback with right number of arguments. --- src/utils/array.js | 92 ++++++++++++++++++++++++++++++++--- src/utils/optimizeCallback.js | 14 +++++- 2 files changed, 97 insertions(+), 9 deletions(-) diff --git a/src/utils/array.js b/src/utils/array.js index 3894e336f0..487e41a085 100644 --- a/src/utils/array.js +++ b/src/utils/array.js @@ -4,6 +4,7 @@ import { format } from './string.js' import { DimensionError } from '../error/DimensionError.js' import { IndexError } from '../error/IndexError.js' import { deepStrictEqual } from './object.js' +import { findNumberOfArguments } from './optimizeCallback.js' /** * Calculate the size of a multi dimensional array. @@ -835,13 +836,52 @@ export function get (array, index) { * @returns {*} The new array with each element being the result of the callback function. */ export function deepMap (value, array, callback) { - return recurse(value, [], array, callback) - function recurse (value, index, array, callback) { + const numberOfArguments = findNumberOfArguments(callback, array) + switch (numberOfArguments) { + case 1: + return recurse1(value) + case 2: + return recurse2(value, []) + case 3: + return recurse3(value, []) + default: + return recurse3(value, []) + } + + function recurse1 (value) { + if (Array.isArray(value)) { + return value.map(function (child) { + // we create a copy of the index array and append the new index value + const results = recurse1(child) + return results + }) + } else { + // invoke the callback function with the right number of arguments + return callback(value) + } + } + + function recurse2 (value, index) { if (Array.isArray(value)) { return value.map(function (child, i) { // we create a copy of the index array and append the new index value index.push(i) - const results = recurse(child, index, array, callback) + const results = recurse2(child, index) + index.pop() + return results + }) + } else { + // invoke the callback function with the right number of arguments + return callback(value, [...index]) + } + } + + function recurse3 (value, index) { + if (Array.isArray(value)) { + return value.map(function (child, i) { + // we create a copy of the index array and append the new index value + index.push(i) + const results = recurse3(child, index) index.pop() return results }) @@ -862,13 +902,51 @@ export function deepMap (value, array, callback) { * @returns {*} The new array with each element being the result of the callback function. */ export function deepForEach (value, array, callback) { - recurse(value, [], array, callback) - function recurse (value, index, array, callback) { + const numberOfArguments = findNumberOfArguments(callback, array) + switch (numberOfArguments) { + case 1: + recurse1(value) + break + case 2: + recurse2(value, []) + break + case 3: + recurse3(value, []) + break + default: + recurse3(value, []) + break + } + + function recurse1 (value) { + if (Array.isArray(value)) { + return value.forEach(function (child) { + recurse1(child) + }) + } else { + // invoke the callback function with the right number of arguments + callback(value) + } + } + + function recurse2 (value, index) { + if (Array.isArray(value)) { + return value.forEach(function (child, i) { + index.push(i) + recurse2(child, index) + index.pop() + }) + } else { + // invoke the callback function with the right number of arguments + callback(value, [...index]) + } + } + + function recurse3 (value, index) { if (Array.isArray(value)) { return value.forEach(function (child, i) { - // we create a copy of the index array and append the new index value index.push(i) - recurse(child, index, array, callback) + recurse3(child, index) index.pop() }) } else { diff --git a/src/utils/optimizeCallback.js b/src/utils/optimizeCallback.js index 68657c3ad6..0d2f4e1b74 100644 --- a/src/utils/optimizeCallback.js +++ b/src/utils/optimizeCallback.js @@ -15,7 +15,7 @@ export function optimizeCallback (callback, array, name) { const firstIndex = (array.isMatrix ? array.size() : arraySize(array)).map(() => 0) const firstValue = array.isMatrix ? array.get(firstIndex) : get(array, firstIndex) const hasSingleSignature = Object.keys(callback.signatures).length === 1 - const numberOfArguments = findNumberOfArguments(callback, firstValue, firstIndex, array) + const numberOfArguments = _typedFindNumberOfArguments(callback, firstValue, firstIndex, array) const fastCallback = hasSingleSignature ? Object.values(callback.signatures)[0] : callback if (numberOfArguments >= 1 && numberOfArguments <= 3) { return (...args) => tryFunctionWithArgs(fastCallback, args.slice(0, numberOfArguments), name, callback.name) @@ -25,7 +25,17 @@ export function optimizeCallback (callback, array, name) { return callback } -export function findNumberOfArguments (callback, value, index, array) { +export function findNumberOfArguments (callback, array) { + if (typed.isTypedFunction(callback)) { + const firstIndex = (array.isMatrix ? array.size() : arraySize(array)).map(() => 0) + const firstValue = array.isMatrix ? array.get(firstIndex) : get(array, firstIndex) + return _typedFindNumberOfArguments(callback, firstValue, firstIndex, array) + } else { + return callback.length + } +} + +function _typedFindNumberOfArguments (callback, value, index, array) { const testArgs = [value, index, array] for (let i = 3; i > 0; i--) { const args = testArgs.slice(0, i) From 8324f63f828d0528ce8ed6d9f7c132b9ca4070b4 Mon Sep 17 00:00:00 2001 From: David Contreras Date: Mon, 16 Sep 2024 16:41:25 -0600 Subject: [PATCH 03/19] Removed return in forEach --- src/utils/array.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/array.js b/src/utils/array.js index 487e41a085..5e68133545 100644 --- a/src/utils/array.js +++ b/src/utils/array.js @@ -920,7 +920,7 @@ export function deepForEach (value, array, callback) { function recurse1 (value) { if (Array.isArray(value)) { - return value.forEach(function (child) { + value.forEach(function (child) { recurse1(child) }) } else { @@ -931,7 +931,7 @@ export function deepForEach (value, array, callback) { function recurse2 (value, index) { if (Array.isArray(value)) { - return value.forEach(function (child, i) { + value.forEach(function (child, i) { index.push(i) recurse2(child, index) index.pop() @@ -944,7 +944,7 @@ export function deepForEach (value, array, callback) { function recurse3 (value, index) { if (Array.isArray(value)) { - return value.forEach(function (child, i) { + value.forEach(function (child, i) { index.push(i) recurse3(child, index) index.pop() From 9c427208f051aabb4589ed3908937aba03ebce72 Mon Sep 17 00:00:00 2001 From: David Contreras Date: Wed, 18 Sep 2024 20:38:28 -0600 Subject: [PATCH 04/19] Clone with slice --- src/utils/array.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/utils/array.js b/src/utils/array.js index 5e68133545..83733a0178 100644 --- a/src/utils/array.js +++ b/src/utils/array.js @@ -872,7 +872,7 @@ export function deepMap (value, array, callback) { }) } else { // invoke the callback function with the right number of arguments - return callback(value, [...index]) + return callback(value, index.slice()) } } @@ -887,7 +887,7 @@ export function deepMap (value, array, callback) { }) } else { // invoke the callback function with the right number of arguments - return callback(value, [...index], array) + return callback(value, index.slice(), array) } } } @@ -938,7 +938,7 @@ export function deepForEach (value, array, callback) { }) } else { // invoke the callback function with the right number of arguments - callback(value, [...index]) + callback(value, index.slice()) } } @@ -951,7 +951,7 @@ export function deepForEach (value, array, callback) { }) } else { // invoke the callback function with the right number of arguments - callback(value, [...index], array) + callback(value, index.slice(), array) } } } From 2ec54adbf4db4abd2ffcbde9b66ddbaf998f8247 Mon Sep 17 00:00:00 2001 From: David Contreras Date: Wed, 18 Sep 2024 20:57:49 -0600 Subject: [PATCH 05/19] Added index benchmarks --- test/benchmark/forEach.js | 10 ++++++++++ test/benchmark/map.js | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/test/benchmark/forEach.js b/test/benchmark/forEach.js index 7f3f3da510..9030c2027a 100644 --- a/test/benchmark/forEach.js +++ b/test/benchmark/forEach.js @@ -43,6 +43,16 @@ new Benchmark.Suite() .add(pad('numberMatrix.forEach(abs.signatures.number)'), () => { numberMatrix.forEach(abs.signatures.number) }) + .add(pad('genericMatrix.forEach(abs+idx)'), () => { + genericMatrix.forEach((x, idx) => abs(x)+idx[0]-idx[1]) + }) + .add(pad('numberMatrix.forEach(abs+idx)'), () => { + numberMatrix.forEach((x, idx) => abs(x)+idx[0]-idx[1]) + }) + .add(pad('forEach(genericMatrix, abs+idx)'), () => { + forEach(genericMatrix, ((x, idx) => abs(x)+idx[0]-idx[1])) + }) + .add() .on('cycle', function (event) { console.log(String(event.target)) }) diff --git a/test/benchmark/map.js b/test/benchmark/map.js index 05b0ea3986..016d258dcd 100644 --- a/test/benchmark/map.js +++ b/test/benchmark/map.js @@ -43,6 +43,15 @@ new Benchmark.Suite() .add(pad('numberMatrix.map(abs.signatures.number)'), () => { numberMatrix.map(abs.signatures.number) }) + .add(pad('map(array, abs + idx)'), () => { + map(array, (x, idx) => abs(x)+idx[0]-idx[1]) + }) + .add(pad('genericMatrix.map(abs + idx)'), () => { + genericMatrix.map((x, idx) => abs(x)+idx[0]-idx[1]) + }) + .add(pad('numberMatrix.map(abs + idx)'), () => { + numberMatrix.map((x, idx) => abs(x)+idx[0]-idx[1]) + }) .on('cycle', function (event) { console.log(String(event.target)) }) From 81f6d7697bcbf62db3e648054ba975c2c8f012ce Mon Sep 17 00:00:00 2001 From: David Contreras Date: Thu, 19 Sep 2024 19:41:04 -0600 Subject: [PATCH 06/19] Format --- test/benchmark/forEach.js | 6 +++--- test/benchmark/map.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/benchmark/forEach.js b/test/benchmark/forEach.js index 9030c2027a..06ad200731 100644 --- a/test/benchmark/forEach.js +++ b/test/benchmark/forEach.js @@ -44,13 +44,13 @@ new Benchmark.Suite() numberMatrix.forEach(abs.signatures.number) }) .add(pad('genericMatrix.forEach(abs+idx)'), () => { - genericMatrix.forEach((x, idx) => abs(x)+idx[0]-idx[1]) + genericMatrix.forEach((x, idx) => abs(x) + idx[0] - idx[1]) }) .add(pad('numberMatrix.forEach(abs+idx)'), () => { - numberMatrix.forEach((x, idx) => abs(x)+idx[0]-idx[1]) + numberMatrix.forEach((x, idx) => abs(x) + idx[0] - idx[1]) }) .add(pad('forEach(genericMatrix, abs+idx)'), () => { - forEach(genericMatrix, ((x, idx) => abs(x)+idx[0]-idx[1])) + forEach(genericMatrix, (x, idx) => abs(x) + idx[0] - idx[1]) }) .add() .on('cycle', function (event) { diff --git a/test/benchmark/map.js b/test/benchmark/map.js index 016d258dcd..6754592971 100644 --- a/test/benchmark/map.js +++ b/test/benchmark/map.js @@ -44,13 +44,13 @@ new Benchmark.Suite() numberMatrix.map(abs.signatures.number) }) .add(pad('map(array, abs + idx)'), () => { - map(array, (x, idx) => abs(x)+idx[0]-idx[1]) + map(array, (x, idx) => abs(x) + idx[0] - idx[1]) }) .add(pad('genericMatrix.map(abs + idx)'), () => { - genericMatrix.map((x, idx) => abs(x)+idx[0]-idx[1]) + genericMatrix.map((x, idx) => abs(x) + idx[0] - idx[1]) }) .add(pad('numberMatrix.map(abs + idx)'), () => { - numberMatrix.map((x, idx) => abs(x)+idx[0]-idx[1]) + numberMatrix.map((x, idx) => abs(x) + idx[0] - idx[1]) }) .on('cycle', function (event) { console.log(String(event.target)) From edc61eb069925079dc84c6827c49c753cc3ec88c Mon Sep 17 00:00:00 2001 From: David Contreras Date: Thu, 19 Sep 2024 21:12:35 -0600 Subject: [PATCH 07/19] Optimize callback returns the number of arguments --- src/function/matrix/filter.js | 2 +- src/function/matrix/forEach.js | 3 ++- src/function/matrix/map.js | 3 ++- src/type/matrix/DenseMatrix.js | 8 ++++---- src/type/matrix/SparseMatrix.js | 4 ++-- src/utils/array.js | 10 ++++------ src/utils/optimizeCallback.js | 25 ++++++++++++++++++------- 7 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/function/matrix/filter.js b/src/function/matrix/filter.js index 1b8c832647..e56e745853 100644 --- a/src/function/matrix/filter.js +++ b/src/function/matrix/filter.js @@ -58,7 +58,7 @@ export const createFilter = /* #__PURE__ */ factory(name, dependencies, ({ typed * @private */ function _filterCallback (x, callback) { - const fastCallback = optimizeCallback(callback, x, 'filter') + const fastCallback = optimizeCallback(callback, x, 'filter')[0] return filter(x, function (value, index, array) { // invoke the callback function with the right number of arguments return fastCallback(value, [index], array) diff --git a/src/function/matrix/forEach.js b/src/function/matrix/forEach.js index 5ceb158815..a8b7c3fa9f 100644 --- a/src/function/matrix/forEach.js +++ b/src/function/matrix/forEach.js @@ -45,5 +45,6 @@ export const createForEach = /* #__PURE__ */ factory(name, dependencies, ({ type * @private */ function _forEach (array, callback) { - deepForEach(array, array, optimizeCallback(callback, array, name)) + const [fastCallback, numberOfArguments] = optimizeCallback(callback, array, name, { detailedError: true }) + deepForEach(array, array, fastCallback, numberOfArguments) } diff --git a/src/function/matrix/map.js b/src/function/matrix/map.js index fb4c94ab82..b295e600f6 100644 --- a/src/function/matrix/map.js +++ b/src/function/matrix/map.js @@ -151,6 +151,7 @@ export const createMap = /* #__PURE__ */ factory(name, dependencies, ({ typed }) * @private */ function _mapArray (array, callback) { - return deepMap(array, array, optimizeCallback(callback, array, name)) + const [fastCallback, numberOfArguments] = optimizeCallback(callback, array, name, { detailedError: true }) + return deepMap(array, array, fastCallback, numberOfArguments) } }) diff --git a/src/type/matrix/DenseMatrix.js b/src/type/matrix/DenseMatrix.js index 35663be902..d2e75b802e 100644 --- a/src/type/matrix/DenseMatrix.js +++ b/src/type/matrix/DenseMatrix.js @@ -537,11 +537,11 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies DenseMatrix.prototype.map = function (callback) { // matrix instance const me = this - const fastCallback = optimizeCallback(callback, me._data, 'map') + const [fastCallback, numberOfArguments] = optimizeCallback(callback, me._data, 'map') // determine the new datatype when the original matrix has datatype defined // TODO: should be done in matrix constructor instead - const data = deepMap(me._data, me, fastCallback) + const data = deepMap(me._data, me, fastCallback, numberOfArguments) const datatype = me._datatype !== undefined ? getArrayDataType(data, typeOf) : undefined @@ -558,8 +558,8 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies DenseMatrix.prototype.forEach = function (callback) { // matrix instance const me = this - const fastCallback = optimizeCallback(callback, me._data, 'forEach') - deepForEach(this._data, me, fastCallback) + const [fastCallback, numberOfArguments] = optimizeCallback(callback, me._data, 'forEach') + deepForEach(this._data, me, fastCallback, numberOfArguments) } /** diff --git a/src/type/matrix/SparseMatrix.js b/src/type/matrix/SparseMatrix.js index 355cfb297d..deed71442e 100644 --- a/src/type/matrix/SparseMatrix.js +++ b/src/type/matrix/SparseMatrix.js @@ -853,7 +853,7 @@ export const createSparseMatrixClass = /* #__PURE__ */ factory(name, dependencie // rows and columns const rows = this._size[0] const columns = this._size[1] - const fastCallback = optimizeCallback(callback, me, 'map') + const fastCallback = optimizeCallback(callback, me, 'map')[0] // invoke callback const invoke = function (v, i, j) { // invoke callback @@ -962,7 +962,7 @@ export const createSparseMatrixClass = /* #__PURE__ */ factory(name, dependencie // rows and columns const rows = this._size[0] const columns = this._size[1] - const fastCallback = optimizeCallback(callback, me, 'forEach') + const fastCallback = optimizeCallback(callback, me, 'forEach')[0] // loop columns for (let j = 0; j < columns; j++) { // k0 <= k < k1 where k0 = _ptr[j] && k1 = _ptr[j+1] diff --git a/src/utils/array.js b/src/utils/array.js index 83733a0178..3cbd3506d0 100644 --- a/src/utils/array.js +++ b/src/utils/array.js @@ -835,9 +835,8 @@ export function get (array, index) { * @param {Function} callback - Function that produces the element of the new Array, taking three arguments: the value of the element, the index of the element, and the Array being processed. * @returns {*} The new array with each element being the result of the callback function. */ -export function deepMap (value, array, callback) { - const numberOfArguments = findNumberOfArguments(callback, array) - switch (numberOfArguments) { +export function deepMap (value, array, callback, numberOfArguments) { + switch (numberOfArguments || findNumberOfArguments(callback, array)) { case 1: return recurse1(value) case 2: @@ -901,9 +900,8 @@ export function deepMap (value, array, callback) { * @param {Function} callback - Function that produces the element of the new Array, taking three arguments: the value of the element, the index of the element, and the Array being processed. * @returns {*} The new array with each element being the result of the callback function. */ -export function deepForEach (value, array, callback) { - const numberOfArguments = findNumberOfArguments(callback, array) - switch (numberOfArguments) { +export function deepForEach (value, array, callback, numberOfArguments) { + switch (numberOfArguments || findNumberOfArguments(callback, array)) { case 1: recurse1(value) break diff --git a/src/utils/optimizeCallback.js b/src/utils/optimizeCallback.js index 0d2f4e1b74..f3f9ab90cc 100644 --- a/src/utils/optimizeCallback.js +++ b/src/utils/optimizeCallback.js @@ -8,21 +8,32 @@ import { typeOf as _typeOf } from './is.js' * @param {Function} callback The original callback function to simplify. * @param {Array|Matrix} array The array that will be used with the callback function. * @param {string} name The name of the function that is using the callback. - * @returns {Function} Returns a simplified version of the callback function. + * @returns {Array} Returns an array with the simplified version of the callback function and it's number of arguments */ -export function optimizeCallback (callback, array, name) { +export function optimizeCallback (callback, array, name, options) { if (typed.isTypedFunction(callback)) { const firstIndex = (array.isMatrix ? array.size() : arraySize(array)).map(() => 0) const firstValue = array.isMatrix ? array.get(firstIndex) : get(array, firstIndex) const hasSingleSignature = Object.keys(callback.signatures).length === 1 const numberOfArguments = _typedFindNumberOfArguments(callback, firstValue, firstIndex, array) - const fastCallback = hasSingleSignature ? Object.values(callback.signatures)[0] : callback - if (numberOfArguments >= 1 && numberOfArguments <= 3) { - return (...args) => tryFunctionWithArgs(fastCallback, args.slice(0, numberOfArguments), name, callback.name) + const fastCallback = hasSingleSignature ? Object.values(callback.signatures)[0] : (...args) => callback(...args) + if (options && options.detailedError) { + if (numberOfArguments >= 1 && numberOfArguments <= 3) { + const limitedCallback = (...args) => tryFunctionWithArgs(fastCallback, args.slice(0, numberOfArguments), name, callback.name) + return [limitedCallback, numberOfArguments] + } else { + const enhancedCallback = (...args) => tryFunctionWithArgs(fastCallback, args, name, callback.name) + return [enhancedCallback, numberOfArguments] + } + } else { + if (numberOfArguments >= 1 && numberOfArguments <= 3) { + return [(...args) => fastCallback(...args.slice(0, numberOfArguments)), numberOfArguments] + } else { + return [(...args) => fastCallback(...args), numberOfArguments] + } } - return (...args) => tryFunctionWithArgs(fastCallback, args, name, callback.name) } - return callback + return [callback, callback.length] } export function findNumberOfArguments (callback, array) { From 9f80b901e6cb12e49279748d356ea65d77b3586b Mon Sep 17 00:00:00 2001 From: David Contreras Date: Thu, 19 Sep 2024 21:12:59 -0600 Subject: [PATCH 08/19] Added tests for callbacks with three arguments. --- test/benchmark/forEach.js | 12 ++++++++++++ test/benchmark/map.js | 9 +++++++++ 2 files changed, 21 insertions(+) diff --git a/test/benchmark/forEach.js b/test/benchmark/forEach.js index 06ad200731..6b720df30f 100644 --- a/test/benchmark/forEach.js +++ b/test/benchmark/forEach.js @@ -52,6 +52,18 @@ new Benchmark.Suite() .add(pad('forEach(genericMatrix, abs+idx)'), () => { forEach(genericMatrix, (x, idx) => abs(x) + idx[0] - idx[1]) }) + .add(pad('genericMatrix.forEach(abs+idx+arr)'), () => { + genericMatrix.forEach((x, idx, X) => abs(x) + idx[0] - idx[1] + X.get([0, 0])) + }) + .add(pad('numberMatrix.forEach(abs+idx+arr)'), () => { + numberMatrix.forEach((x, idx, X) => abs(x) + idx[0] - idx[1] + X.get([0, 0])) + }) + .add(pad('forEach(genericMatrix, abs+idx+arr)'), () => { + forEach(genericMatrix, (x, idx, X) => abs(x) + idx[0] - idx[1] + X.get([0, 0])) + }) + .add(pad('forEach(array, abs+idx+arr)'), () => { + forEach(array, (x, idx, X) => abs(x) + idx[0] - idx[1] + X[0][0]) + }) .add() .on('cycle', function (event) { console.log(String(event.target)) diff --git a/test/benchmark/map.js b/test/benchmark/map.js index 6754592971..7581b3c5ed 100644 --- a/test/benchmark/map.js +++ b/test/benchmark/map.js @@ -52,6 +52,15 @@ new Benchmark.Suite() .add(pad('numberMatrix.map(abs + idx)'), () => { numberMatrix.map((x, idx) => abs(x) + idx[0] - idx[1]) }) + .add(pad('map(array, abs + idx + arr)'), () => { + map(array, (x, idx, X) => abs(x) + idx[0] - idx[1] + X[0][0]) + }) + .add(pad('genericMatrix.map(abs + idx + matrix)'), () => { + genericMatrix.map((x, idx, X) => abs(x) + idx[0] - idx[1] + X.get([0, 0])) + }) + .add(pad('numberMatrix.map(abs + idx + matrix)'), () => { + numberMatrix.map((x, idx, X) => abs(x) + idx[0] - idx[1] + X.get([0, 0])) + }) .on('cycle', function (event) { console.log(String(event.target)) }) From a5618b5a25d9bad0b193108c23c688508bb2e6cd Mon Sep 17 00:00:00 2001 From: David Contreras Date: Fri, 20 Sep 2024 21:12:17 -0600 Subject: [PATCH 09/19] optimizeCallback returns a non typed function. --- src/function/matrix/filter.js | 2 +- src/function/matrix/forEach.js | 4 ++-- src/function/matrix/map.js | 4 ++-- src/type/matrix/DenseMatrix.js | 8 ++++---- src/type/matrix/SparseMatrix.js | 4 ++-- src/utils/optimizeCallback.js | 34 +++++++++++++++++++++------------ 6 files changed, 33 insertions(+), 23 deletions(-) diff --git a/src/function/matrix/filter.js b/src/function/matrix/filter.js index e56e745853..1b8c832647 100644 --- a/src/function/matrix/filter.js +++ b/src/function/matrix/filter.js @@ -58,7 +58,7 @@ export const createFilter = /* #__PURE__ */ factory(name, dependencies, ({ typed * @private */ function _filterCallback (x, callback) { - const fastCallback = optimizeCallback(callback, x, 'filter')[0] + const fastCallback = optimizeCallback(callback, x, 'filter') return filter(x, function (value, index, array) { // invoke the callback function with the right number of arguments return fastCallback(value, [index], array) diff --git a/src/function/matrix/forEach.js b/src/function/matrix/forEach.js index a8b7c3fa9f..21fb8baf00 100644 --- a/src/function/matrix/forEach.js +++ b/src/function/matrix/forEach.js @@ -45,6 +45,6 @@ export const createForEach = /* #__PURE__ */ factory(name, dependencies, ({ type * @private */ function _forEach (array, callback) { - const [fastCallback, numberOfArguments] = optimizeCallback(callback, array, name, { detailedError: true }) - deepForEach(array, array, fastCallback, numberOfArguments) + const fastCallback = optimizeCallback(callback, array, name, { detailedError: true }) + deepForEach(array, array, fastCallback, fastCallback.length) } diff --git a/src/function/matrix/map.js b/src/function/matrix/map.js index b295e600f6..cd09833dfc 100644 --- a/src/function/matrix/map.js +++ b/src/function/matrix/map.js @@ -151,7 +151,7 @@ export const createMap = /* #__PURE__ */ factory(name, dependencies, ({ typed }) * @private */ function _mapArray (array, callback) { - const [fastCallback, numberOfArguments] = optimizeCallback(callback, array, name, { detailedError: true }) - return deepMap(array, array, fastCallback, numberOfArguments) + const fastCallback = optimizeCallback(callback, array, name, { detailedError: true }) + return deepMap(array, array, fastCallback, fastCallback.length) } }) diff --git a/src/type/matrix/DenseMatrix.js b/src/type/matrix/DenseMatrix.js index d2e75b802e..bbbd3eea4a 100644 --- a/src/type/matrix/DenseMatrix.js +++ b/src/type/matrix/DenseMatrix.js @@ -537,11 +537,11 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies DenseMatrix.prototype.map = function (callback) { // matrix instance const me = this - const [fastCallback, numberOfArguments] = optimizeCallback(callback, me._data, 'map') + const fastCallback = optimizeCallback(callback, me._data, 'map') // determine the new datatype when the original matrix has datatype defined // TODO: should be done in matrix constructor instead - const data = deepMap(me._data, me, fastCallback, numberOfArguments) + const data = deepMap(me._data, me, fastCallback, fastCallback.length) const datatype = me._datatype !== undefined ? getArrayDataType(data, typeOf) : undefined @@ -558,8 +558,8 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies DenseMatrix.prototype.forEach = function (callback) { // matrix instance const me = this - const [fastCallback, numberOfArguments] = optimizeCallback(callback, me._data, 'forEach') - deepForEach(this._data, me, fastCallback, numberOfArguments) + const fastCallback = optimizeCallback(callback, me._data, 'forEach') + deepForEach(this._data, me, fastCallback, fastCallback.length) } /** diff --git a/src/type/matrix/SparseMatrix.js b/src/type/matrix/SparseMatrix.js index deed71442e..355cfb297d 100644 --- a/src/type/matrix/SparseMatrix.js +++ b/src/type/matrix/SparseMatrix.js @@ -853,7 +853,7 @@ export const createSparseMatrixClass = /* #__PURE__ */ factory(name, dependencie // rows and columns const rows = this._size[0] const columns = this._size[1] - const fastCallback = optimizeCallback(callback, me, 'map')[0] + const fastCallback = optimizeCallback(callback, me, 'map') // invoke callback const invoke = function (v, i, j) { // invoke callback @@ -962,7 +962,7 @@ export const createSparseMatrixClass = /* #__PURE__ */ factory(name, dependencie // rows and columns const rows = this._size[0] const columns = this._size[1] - const fastCallback = optimizeCallback(callback, me, 'forEach')[0] + const fastCallback = optimizeCallback(callback, me, 'forEach') // loop columns for (let j = 0; j < columns; j++) { // k0 <= k < k1 where k0 = _ptr[j] && k1 = _ptr[j+1] diff --git a/src/utils/optimizeCallback.js b/src/utils/optimizeCallback.js index f3f9ab90cc..049aad5eed 100644 --- a/src/utils/optimizeCallback.js +++ b/src/utils/optimizeCallback.js @@ -16,24 +16,34 @@ export function optimizeCallback (callback, array, name, options) { const firstValue = array.isMatrix ? array.get(firstIndex) : get(array, firstIndex) const hasSingleSignature = Object.keys(callback.signatures).length === 1 const numberOfArguments = _typedFindNumberOfArguments(callback, firstValue, firstIndex, array) - const fastCallback = hasSingleSignature ? Object.values(callback.signatures)[0] : (...args) => callback(...args) if (options && options.detailedError) { - if (numberOfArguments >= 1 && numberOfArguments <= 3) { - const limitedCallback = (...args) => tryFunctionWithArgs(fastCallback, args.slice(0, numberOfArguments), name, callback.name) - return [limitedCallback, numberOfArguments] - } else { - const enhancedCallback = (...args) => tryFunctionWithArgs(fastCallback, args, name, callback.name) - return [enhancedCallback, numberOfArguments] + const fastCallback = hasSingleSignature ? Object.values(callback.signatures)[0] : callback + switch (numberOfArguments) { + case 1: + return (val) => tryFunctionWithArgs(fastCallback, [val], name, callback.name) + case 2: + return (val, idx) => tryFunctionWithArgs(fastCallback, [val, idx], name, callback.name) + case 3: + return (val, idx, array) => tryFunctionWithArgs(fastCallback, [val, idx, array], name, callback.name) + default: + return (...args) => tryFunctionWithArgs(fastCallback, args, name, callback.name) } + } else if (hasSingleSignature) { + return Object.values(callback.signatures)[0] } else { - if (numberOfArguments >= 1 && numberOfArguments <= 3) { - return [(...args) => fastCallback(...args.slice(0, numberOfArguments)), numberOfArguments] - } else { - return [(...args) => fastCallback(...args), numberOfArguments] + switch (numberOfArguments) { + case 1: + return val => callback(val) + case 2: + return (val, idx) => callback(val, idx) + case 3: + return (val, idx, array) => callback(val, idx, array) + default: + return callback } } } - return [callback, callback.length] + return callback } export function findNumberOfArguments (callback, array) { From c7a4210197fc4dc4fa53a10d9bc4898938e1349e Mon Sep 17 00:00:00 2001 From: David Contreras Date: Mon, 30 Sep 2024 22:56:11 -0600 Subject: [PATCH 10/19] Optimize recursre functions by mapping the last dimension. --- src/utils/array.js | 96 +++++++++++++++++++++++++++------------------- 1 file changed, 56 insertions(+), 40 deletions(-) diff --git a/src/utils/array.js b/src/utils/array.js index 3cbd3506d0..76b8bc627d 100644 --- a/src/utils/array.js +++ b/src/utils/array.js @@ -836,57 +836,65 @@ export function get (array, index) { * @returns {*} The new array with each element being the result of the callback function. */ export function deepMap (value, array, callback, numberOfArguments) { + const size = arraySize(value) + const N = size.length - 1 switch (numberOfArguments || findNumberOfArguments(callback, array)) { case 1: - return recurse1(value) + return recurse1(value, 0) case 2: - return recurse2(value, []) + return recurse2(value, size.map(() => null), 0) case 3: - return recurse3(value, []) + return recurse3(value, size.map(() => null), 0) default: - return recurse3(value, []) + return recurse3(value, size.map(() => null), 0) } - function recurse1 (value) { - if (Array.isArray(value)) { + function recurse1 (value, depth) { + if (depth < N) { return value.map(function (child) { // we create a copy of the index array and append the new index value - const results = recurse1(child) + const results = recurse1(child, depth + 1) return results }) } else { // invoke the callback function with the right number of arguments - return callback(value) + return value.map(v => callback(v)) } } - function recurse2 (value, index) { - if (Array.isArray(value)) { + function recurse2 (value, index, depth) { + if (depth < N) { return value.map(function (child, i) { // we create a copy of the index array and append the new index value - index.push(i) - const results = recurse2(child, index) - index.pop() + index[depth] = i + const results = recurse2(child, index, depth + 1) + index[depth] = null return results }) } else { // invoke the callback function with the right number of arguments - return callback(value, index.slice()) + return value.map((v, i) => { + index[depth] = i + return callback(v, index.slice()) + }) } } - function recurse3 (value, index) { - if (Array.isArray(value)) { + function recurse3 (value, index, depth) { + if (depth < N) { return value.map(function (child, i) { // we create a copy of the index array and append the new index value - index.push(i) - const results = recurse3(child, index) - index.pop() + index[depth] = i + const results = recurse3(child, index, depth + 1) + index[depth] = null return results }) } else { // invoke the callback function with the right number of arguments - return callback(value, index.slice(), array) + return value.map((v, i) => { + index[depth] = i + return callback(v, index.slice(), array) + }) } } } @@ -901,55 +909,63 @@ export function deepMap (value, array, callback, numberOfArguments) { * @returns {*} The new array with each element being the result of the callback function. */ export function deepForEach (value, array, callback, numberOfArguments) { + const size = arraySize(value) + const N = size.length - 1 switch (numberOfArguments || findNumberOfArguments(callback, array)) { case 1: - recurse1(value) + recurse1(value, 0) break case 2: - recurse2(value, []) + recurse2(value, size.map(() => null), 0) break case 3: - recurse3(value, []) + recurse3(value, size.map(() => null), 0) break default: - recurse3(value, []) + recurse3(value, size.map(() => null), 0) break } - function recurse1 (value) { - if (Array.isArray(value)) { + function recurse1 (value, depth) { + if (depth < N) { value.forEach(function (child) { - recurse1(child) + recurse1(child, depth + 1) }) } else { // invoke the callback function with the right number of arguments - callback(value) + value.forEach(v => callback(v)) } } - function recurse2 (value, index) { - if (Array.isArray(value)) { + function recurse2 (value, index, depth) { + if (depth < N) { value.forEach(function (child, i) { - index.push(i) - recurse2(child, index) - index.pop() + index[depth] = i + recurse2(child, index, depth + 1) + index[depth] = null }) } else { // invoke the callback function with the right number of arguments - callback(value, index.slice()) + value.forEach((v, i) => { + index[depth] = i + callback(v, index.slice()) + }) } } - function recurse3 (value, index) { - if (Array.isArray(value)) { + function recurse3 (value, index, depth) { + if (depth < N) { value.forEach(function (child, i) { - index.push(i) - recurse3(child, index) - index.pop() + index[depth] = i + recurse3(child, index, depth + 1) + index[depth] = null }) } else { // invoke the callback function with the right number of arguments - callback(value, index.slice(), array) + value.forEach((v, i) => { + index[depth] = i + callback(v, index.slice(), array) + }) } } } From f98a164b0a1f3aa65f6c570fc622fffe93e17a1d Mon Sep 17 00:00:00 2001 From: David Contreras Date: Mon, 16 Dec 2024 11:01:19 -0600 Subject: [PATCH 11/19] Updated tests --- test/benchmark/forEach.js | 17 +++++++---------- test/benchmark/map.js | 15 ++++++--------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/test/benchmark/forEach.js b/test/benchmark/forEach.js index b9341854b1..534ebb0caf 100644 --- a/test/benchmark/forEach.js +++ b/test/benchmark/forEach.js @@ -6,9 +6,6 @@ const genericMatrix = map(ones(10, 10, 'dense'), _ => round(random(-5, 5), 2)) const numberMatrix = new DenseMatrix(genericMatrix, 'number') const array = genericMatrix.toArray() -// console.log('data', array) -// console.log('abs(data)', abs(array))npm run - const bench = new Bench({ time: 100, iterations: 100 }) .add('abs(genericMatrix)', () => { abs(genericMatrix) @@ -43,25 +40,25 @@ const bench = new Bench({ time: 100, iterations: 100 }) .add('numberMatrix.forEach(abs.signatures.number)', () => { numberMatrix.forEach(abs.signatures.number) }) - .add(pad('genericMatrix.forEach(abs+idx)'), () => { + .add('genericMatrix.forEach(abs+idx)', () => { genericMatrix.forEach((x, idx) => abs(x) + idx[0] - idx[1]) }) - .add(pad('numberMatrix.forEach(abs+idx)'), () => { + .add('numberMatrix.forEach(abs+idx)', () => { numberMatrix.forEach((x, idx) => abs(x) + idx[0] - idx[1]) }) - .add(pad('forEach(genericMatrix, abs+idx)'), () => { + .add('forEach(genericMatrix, abs+idx)', () => { forEach(genericMatrix, (x, idx) => abs(x) + idx[0] - idx[1]) }) - .add(pad('genericMatrix.forEach(abs+idx+arr)'), () => { + .add('genericMatrix.forEach(abs+idx+arr)', () => { genericMatrix.forEach((x, idx, X) => abs(x) + idx[0] - idx[1] + X.get([0, 0])) }) - .add(pad('numberMatrix.forEach(abs+idx+arr)'), () => { + .add('numberMatrix.forEach(abs+idx+arr)', () => { numberMatrix.forEach((x, idx, X) => abs(x) + idx[0] - idx[1] + X.get([0, 0])) }) - .add(pad('forEach(genericMatrix, abs+idx+arr)'), () => { + .add('forEach(genericMatrix, abs+idx+arr)', () => { forEach(genericMatrix, (x, idx, X) => abs(x) + idx[0] - idx[1] + X.get([0, 0])) }) - .add(pad('forEach(array, abs+idx+arr)'), () => { + .add('forEach(array, abs+idx+arr)', () => { forEach(array, (x, idx, X) => abs(x) + idx[0] - idx[1] + X[0][0]) }) .add() diff --git a/test/benchmark/map.js b/test/benchmark/map.js index 338a46e682..8c4b68b61e 100644 --- a/test/benchmark/map.js +++ b/test/benchmark/map.js @@ -6,9 +6,6 @@ const genericMatrix = map(ones(10, 10, 'dense'), _ => round(random(-5, 5), 2)) const numberMatrix = new DenseMatrix(genericMatrix, 'number') const array = genericMatrix.toArray() -// console.log('data', array) -// console.log('abs(data)', abs(array))npm run - const bench = new Bench({ time: 100, iterations: 100 }) .add('abs(genericMatrix)', () => { abs(genericMatrix) @@ -43,22 +40,22 @@ const bench = new Bench({ time: 100, iterations: 100 }) .add('numberMatrix.map(abs.signatures.number)', () => { numberMatrix.map(abs.signatures.number) }) - .add(pad('map(array, abs + idx)'), () => { + .add('map(array, abs + idx)', () => { map(array, (x, idx) => abs(x) + idx[0] - idx[1]) }) - .add(pad('genericMatrix.map(abs + idx)'), () => { + .add('genericMatrix.map(abs + idx)', () => { genericMatrix.map((x, idx) => abs(x) + idx[0] - idx[1]) }) - .add(pad('numberMatrix.map(abs + idx)'), () => { + .add('numberMatrix.map(abs + idx)', () => { numberMatrix.map((x, idx) => abs(x) + idx[0] - idx[1]) }) - .add(pad('map(array, abs + idx + arr)'), () => { + .add('map(array, abs + idx + arr)', () => { map(array, (x, idx, X) => abs(x) + idx[0] - idx[1] + X[0][0]) }) - .add(pad('genericMatrix.map(abs + idx + matrix)'), () => { + .add('genericMatrix.map(abs + idx + matrix)', () => { genericMatrix.map((x, idx, X) => abs(x) + idx[0] - idx[1] + X.get([0, 0])) }) - .add(pad('numberMatrix.map(abs + idx + matrix)'), () => { + .add('numberMatrix.map(abs + idx + matrix)', () => { numberMatrix.map((x, idx, X) => abs(x) + idx[0] - idx[1] + X.get([0, 0])) }) .on('cycle', function (event) { From e59624a836a665374b6832b43fedb277d78870c0 Mon Sep 17 00:00:00 2001 From: David Contreras Date: Mon, 3 Feb 2025 08:35:20 -0600 Subject: [PATCH 12/19] Number of arguments in functions --- src/utils/optimizeCallback.js | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/utils/optimizeCallback.js b/src/utils/optimizeCallback.js index 049aad5eed..033a875d56 100644 --- a/src/utils/optimizeCallback.js +++ b/src/utils/optimizeCallback.js @@ -12,8 +12,7 @@ import { typeOf as _typeOf } from './is.js' */ export function optimizeCallback (callback, array, name, options) { if (typed.isTypedFunction(callback)) { - const firstIndex = (array.isMatrix ? array.size() : arraySize(array)).map(() => 0) - const firstValue = array.isMatrix ? array.get(firstIndex) : get(array, firstIndex) + const [firstValue, firstIndex] = findFirstValueAndIndex(array) const hasSingleSignature = Object.keys(callback.signatures).length === 1 const numberOfArguments = _typedFindNumberOfArguments(callback, firstValue, firstIndex, array) if (options && options.detailedError) { @@ -23,8 +22,6 @@ export function optimizeCallback (callback, array, name, options) { return (val) => tryFunctionWithArgs(fastCallback, [val], name, callback.name) case 2: return (val, idx) => tryFunctionWithArgs(fastCallback, [val, idx], name, callback.name) - case 3: - return (val, idx, array) => tryFunctionWithArgs(fastCallback, [val, idx, array], name, callback.name) default: return (...args) => tryFunctionWithArgs(fastCallback, args, name, callback.name) } @@ -36,8 +33,6 @@ export function optimizeCallback (callback, array, name, options) { return val => callback(val) case 2: return (val, idx) => callback(val, idx) - case 3: - return (val, idx, array) => callback(val, idx, array) default: return callback } @@ -46,16 +41,35 @@ export function optimizeCallback (callback, array, name, options) { return callback } +function findFirstValueAndIndex (array) { + const firstIndex = (array.isMatrix ? array.size() : arraySize(array)).map(() => 0) + const firstValue = array.isMatrix ? array.get(firstIndex) : get(array, firstIndex) + + return [firstValue, firstIndex] +} + export function findNumberOfArguments (callback, array) { + const [firstValue, firstIndex] = findFirstValueAndIndex(array) if (typed.isTypedFunction(callback)) { - const firstIndex = (array.isMatrix ? array.size() : arraySize(array)).map(() => 0) - const firstValue = array.isMatrix ? array.get(firstIndex) : get(array, firstIndex) return _typedFindNumberOfArguments(callback, firstValue, firstIndex, array) } else { - return callback.length + return _fnFindNumberOfArguments(callback, firstValue, firstIndex) } } +function _fnFindNumberOfArguments (callback, value, index) { + if (callback.length === 0) return 3 + try { + callback(value) + return 1 + } catch {} + try { + callback(value, index) + return 2 + } catch {} + return 3 +} + function _typedFindNumberOfArguments (callback, value, index, array) { const testArgs = [value, index, array] for (let i = 3; i > 0; i--) { From a6e052655e2b1cbfe17e85468682b6baada12320 Mon Sep 17 00:00:00 2001 From: David Contreras Date: Mon, 3 Feb 2025 16:38:31 -0600 Subject: [PATCH 13/19] feat: add findFirst utility and enhance array mapping functions --- src/function/matrix/forEach.js | 2 +- src/function/matrix/map.js | 2 +- src/utils/array.js | 144 ++++------------------------ src/utils/collection.js | 11 ++- src/utils/iterable.js | 132 +++++++++++++++++++++++++ test/benchmark/map.js | 6 -- test/unit-tests/utils/array.test.js | 19 ++++ 7 files changed, 184 insertions(+), 132 deletions(-) create mode 100644 src/utils/iterable.js diff --git a/src/function/matrix/forEach.js b/src/function/matrix/forEach.js index f05563d8c0..bfb53f0362 100644 --- a/src/function/matrix/forEach.js +++ b/src/function/matrix/forEach.js @@ -53,5 +53,5 @@ export const createForEach = /* #__PURE__ */ factory(name, dependencies, ({ type */ function _forEach (array, callback) { const fastCallback = optimizeCallback(callback, array, name, { detailedError: true }) - deepForEach(array, array, fastCallback, fastCallback.length) + deepForEach(array, fastCallback, fastCallback.length) } diff --git a/src/function/matrix/map.js b/src/function/matrix/map.js index cd09833dfc..b08e649c73 100644 --- a/src/function/matrix/map.js +++ b/src/function/matrix/map.js @@ -152,6 +152,6 @@ export const createMap = /* #__PURE__ */ factory(name, dependencies, ({ typed }) */ function _mapArray (array, callback) { const fastCallback = optimizeCallback(callback, array, name, { detailedError: true }) - return deepMap(array, array, fastCallback, fastCallback.length) + return deepMap(array, fastCallback, fastCallback.length) } }) diff --git a/src/utils/array.js b/src/utils/array.js index 76b8bc627d..a952ea59e9 100644 --- a/src/utils/array.js +++ b/src/utils/array.js @@ -4,7 +4,7 @@ import { format } from './string.js' import { DimensionError } from '../error/DimensionError.js' import { IndexError } from '../error/IndexError.js' import { deepStrictEqual } from './object.js' -import { findNumberOfArguments } from './optimizeCallback.js' +import { map as iterableMap, forEach as iterableForEach } from './iterable.js' /** * Calculate the size of a multi dimensional array. @@ -24,6 +24,22 @@ export function arraySize (x) { return s } +/** + * Recursively finds the first non-array element in a nested array structure. + * + * @param {Array} x - The nested array to search through. + * @returns {{value: *, index: number[]}} An object containing the first non-array element found and its index path. + */ +export function findFirst (x) { + const idx = [] + + while (Array.isArray(x)) { + idx.push(0) + x = x[0] + } + return { value: x, index: idx } +} + /** * Recursively validate whether each element in a multi dimensional array * has a size corresponding to the provided size array. @@ -835,68 +851,8 @@ export function get (array, index) { * @param {Function} callback - Function that produces the element of the new Array, taking three arguments: the value of the element, the index of the element, and the Array being processed. * @returns {*} The new array with each element being the result of the callback function. */ -export function deepMap (value, array, callback, numberOfArguments) { - const size = arraySize(value) - const N = size.length - 1 - switch (numberOfArguments || findNumberOfArguments(callback, array)) { - case 1: - return recurse1(value, 0) - case 2: - return recurse2(value, size.map(() => null), 0) - case 3: - return recurse3(value, size.map(() => null), 0) - default: - return recurse3(value, size.map(() => null), 0) - } - - function recurse1 (value, depth) { - if (depth < N) { - return value.map(function (child) { - // we create a copy of the index array and append the new index value - const results = recurse1(child, depth + 1) - return results - }) - } else { - // invoke the callback function with the right number of arguments - return value.map(v => callback(v)) - } - } - - function recurse2 (value, index, depth) { - if (depth < N) { - return value.map(function (child, i) { - // we create a copy of the index array and append the new index value - index[depth] = i - const results = recurse2(child, index, depth + 1) - index[depth] = null - return results - }) - } else { - // invoke the callback function with the right number of arguments - return value.map((v, i) => { - index[depth] = i - return callback(v, index.slice()) - }) - } - } - - function recurse3 (value, index, depth) { - if (depth < N) { - return value.map(function (child, i) { - // we create a copy of the index array and append the new index value - index[depth] = i - const results = recurse3(child, index, depth + 1) - index[depth] = null - return results - }) - } else { - // invoke the callback function with the right number of arguments - return value.map((v, i) => { - index[depth] = i - return callback(v, index.slice(), array) - }) - } - } +export function deepMap (array, callback, numberOfArguments) { + return iterableMap(array, callback, false, numberOfArguments !== 1, array) } /** @@ -908,66 +864,8 @@ export function deepMap (value, array, callback, numberOfArguments) { * @param {Function} callback - Function that produces the element of the new Array, taking three arguments: the value of the element, the index of the element, and the Array being processed. * @returns {*} The new array with each element being the result of the callback function. */ -export function deepForEach (value, array, callback, numberOfArguments) { - const size = arraySize(value) - const N = size.length - 1 - switch (numberOfArguments || findNumberOfArguments(callback, array)) { - case 1: - recurse1(value, 0) - break - case 2: - recurse2(value, size.map(() => null), 0) - break - case 3: - recurse3(value, size.map(() => null), 0) - break - default: - recurse3(value, size.map(() => null), 0) - break - } - - function recurse1 (value, depth) { - if (depth < N) { - value.forEach(function (child) { - recurse1(child, depth + 1) - }) - } else { - // invoke the callback function with the right number of arguments - value.forEach(v => callback(v)) - } - } - - function recurse2 (value, index, depth) { - if (depth < N) { - value.forEach(function (child, i) { - index[depth] = i - recurse2(child, index, depth + 1) - index[depth] = null - }) - } else { - // invoke the callback function with the right number of arguments - value.forEach((v, i) => { - index[depth] = i - callback(v, index.slice()) - }) - } - } - - function recurse3 (value, index, depth) { - if (depth < N) { - value.forEach(function (child, i) { - index[depth] = i - recurse3(child, index, depth + 1) - index[depth] = null - }) - } else { - // invoke the callback function with the right number of arguments - value.forEach((v, i) => { - index[depth] = i - callback(v, index.slice(), array) - }) - } - } +export function deepForEach (array, callback, numberOfArguments) { + iterableForEach(array, callback, false, numberOfArguments !== 1, array) } /** diff --git a/src/utils/collection.js b/src/utils/collection.js index 8c740fc98c..c458d9ec65 100644 --- a/src/utils/collection.js +++ b/src/utils/collection.js @@ -1,6 +1,6 @@ import { isCollection, isMatrix } from './is.js' import { IndexError } from '../error/IndexError.js' -import { arraySize } from './array.js' +import { arraySize, findFirst as arrayFindFirst } from './array.js' import { _switch } from './switch.js' /** @@ -18,6 +18,15 @@ export function containsCollections (array) { return false } +export function findFirst (array) { + if (isMatrix(array)) { + const idx = array.size().map(() => 0) + return { value: array.get(idx), index: idx } + } else { + return arrayFindFirst(array) + } +} + /** * Recursively loop over all elements in a given multi dimensional array * and invoke the callback on each of the elements. diff --git a/src/utils/iterable.js b/src/utils/iterable.js new file mode 100644 index 0000000000..bbf9af21ea --- /dev/null +++ b/src/utils/iterable.js @@ -0,0 +1,132 @@ +/** + * Maps each element of the array using the provided callback function. + * + * @param {Array} array - The array to be mapped. + * @param {Function|TypedFunction} callback - The function to execute on each element. + * @param {boolean} [isHomogeneous=false] - Whether the array is homogeneous. + * @param {boolean} [callbackIsIndexed=true] - Whether the callback uses the parameter index. + * @param {Object} [thisArg] - The value of this provided for the call to the callback function. + * @returns {Array} The new array with the results of the callback function. + */ +export function map (array, callback, isHomogeneous = false, callbackIsIndexed = true, thisArg) { + if (callbackIsIndexed) { + thisArg = thisArg || array + if (isHomogeneous) { + return array.map((value, index) => recurseIndexedHomogeneous(value, [index], 1)) + } else { + return array.map((value, index) => recurseIndexedHeterogeneous(value, [index], 1)) + } + } else { + if (isHomogeneous) { + return array.map(function rec (value) { + if (Array.isArray(value[0])) { + return value.map(rec) + } else { + return value.map(callback) + } + }) + } else { + return array.map(function rec (value) { + if (Array.isArray(value)) { + return value.map(rec) + } else { + return callback(value) + } + }) + } + } + + function recurseIndexedHeterogeneous (value, index, depth) { + if (Array.isArray(value)) { + return value.map(function (child, i) { + index[depth] = i + const results = recurseIndexedHeterogeneous(child, index, depth + 1) + index[depth] = null + return results + }) + } else { + return callback(value, index.slice(), thisArg) + } + } + + function recurseIndexedHomogeneous (value, index, depth) { + if (Array.isArray(value[0])) { + return value.map(function (child, i) { + index[depth] = i + const results = recurseIndexedHomogeneous(child, index, depth + 1) + index[depth] = null + return results + }) + } else { + return value.map((v, i) => { + index[depth] = i + return callback(v, index.slice(), thisArg) + }) + } + } +} + +/** + * Applies a callback function to each element of the array using .forEach. + * + * @param {Array} array - The array to be iterated over. + * @param {Function|TypedFunction} callback - The function to execute on each element. + * @param {boolean} [isHomogeneous=false] - Whether the array is homogeneous. + * @param {boolean} [callbackIsIndexed=true] - Whether the callback uses the parameter index. + * @param {Object} [thisArg] - The value of this provided for the call to the callback function. + */ +export function forEach (array, callback, isHomogeneous = false, callbackIsIndexed = true, thisArg) { + if (callbackIsIndexed) { + thisArg = thisArg || array + if (isHomogeneous) { + array.forEach((value, index) => recurseIndexedHomogeneous(value, [index], 1)) + } else { + array.forEach((value, index) => recurseIndexedHeterogeneous(value, [index], 1)) + } + } else { + if (isHomogeneous) { + array.forEach(function rec (value) { + if (Array.isArray(value[0])) { + value.forEach(rec) + } else { + value.forEach(callback) + } + }) + } else { + array.forEach(function rec (value) { + if (Array.isArray(value)) { + value.forEach(rec) + } else { + callback(value) + } + }) + } + } + + function recurseIndexedHeterogeneous (value, index, depth) { + if (Array.isArray(value)) { + value.forEach(function (child, i) { + index[depth] = i + recurseIndexedHeterogeneous(child, index, depth + 1) + index[depth] = null + }) + } else { + callback(value, index.slice(), thisArg) + } + } + + function recurseIndexedHomogeneous (value, index, depth) { + if (Array.isArray(value[0])) { + value.forEach(function (child, i) { + index[depth] = i + recurseIndexedHomogeneous(child, index, depth + 1) + index[depth] = null + }) + } else { + value.forEach((v, i) => { + index[depth] = i + callback(v, index.slice(), thisArg) + }) + } + } +} diff --git a/test/benchmark/map.js b/test/benchmark/map.js index 8c4b68b61e..7da018e3a1 100644 --- a/test/benchmark/map.js +++ b/test/benchmark/map.js @@ -58,12 +58,6 @@ const bench = new Bench({ time: 100, iterations: 100 }) .add('numberMatrix.map(abs + idx + matrix)', () => { numberMatrix.map((x, idx, X) => abs(x) + idx[0] - idx[1] + X.get([0, 0])) }) - .on('cycle', function (event) { - console.log(String(event.target)) - }) - .on('complete', function () { - }) - .run() bench.addEventListener('cycle', (event) => console.log(formatTaskResult(bench, event.task))) await bench.run() diff --git a/test/unit-tests/utils/array.test.js b/test/unit-tests/utils/array.test.js index d87ee32cd3..1cb2e36e85 100644 --- a/test/unit-tests/utils/array.test.js +++ b/test/unit-tests/utils/array.test.js @@ -2,6 +2,7 @@ import assert from 'assert' import math from '../../../src/defaultInstance.js' import { arraySize, + findFirst, flatten, generalize, identify, @@ -59,6 +60,24 @@ describe('util.array', function () { }) }) + describe('findFirst', function () { + it('should find first value and index of a 1D array', assert.deepStrictEqual( + findFirst([1]), { value: 1, index: [0] } + )) + + it('should find first value and index of a 2D array', assert.deepStrictEqual( + findFirst([[1]]), { value: 1, index: [0, 0] } + )) + + it('should find first value and index of a 3D array', assert.deepStrictEqual( + findFirst([[[1]]]), { value: 1, index: [0, 0, 0] } + )) + + it('should find first value and index of an array with non homogeneous size', assert.deepStrictEqual( + findFirst([[[1]], 1]), { value: 1, index: [0, 0, 0] } + )) + }) + describe('resize', function () { it('should resize a scalar', function () { const a = 0 From a0e7ca5f82df3161241ba17ec08106b769c730fb Mon Sep 17 00:00:00 2001 From: David Contreras Date: Mon, 3 Feb 2025 17:20:33 -0600 Subject: [PATCH 14/19] fix deepForEach and enhance forEach utility for better readability --- src/utils/collection.js | 12 +++--------- src/utils/iterable.js | 16 +++++++++------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/utils/collection.js b/src/utils/collection.js index c458d9ec65..062455d2eb 100644 --- a/src/utils/collection.js +++ b/src/utils/collection.js @@ -2,6 +2,7 @@ import { isCollection, isMatrix } from './is.js' import { IndexError } from '../error/IndexError.js' import { arraySize, findFirst as arrayFindFirst } from './array.js' import { _switch } from './switch.js' +import { forEach as iterableForEach } from './iterable.js' /** * Test whether an array contains collections @@ -36,16 +37,9 @@ export function findFirst (array) { */ export function deepForEach (array, callback) { if (isMatrix(array)) { - recurse(array.valueOf()) + array.forEach(callback) } else { - recurse(array) - } - function recurse (array) { - if (Array.isArray(array)) { - array.forEach(value => recurse(value)) - } else { - callback(array) - } + iterableForEach(array, callback, false, false, array) } } diff --git a/src/utils/iterable.js b/src/utils/iterable.js index bbf9af21ea..12b6b0097c 100644 --- a/src/utils/iterable.js +++ b/src/utils/iterable.js @@ -85,13 +85,7 @@ export function forEach (array, callback, isHomogeneous = false, callbackIsIndex } } else { if (isHomogeneous) { - array.forEach(function rec (value) { - if (Array.isArray(value[0])) { - value.forEach(rec) - } else { - value.forEach(callback) - } - }) + recurseHomogeneous(array) } else { array.forEach(function rec (value) { if (Array.isArray(value)) { @@ -103,6 +97,14 @@ export function forEach (array, callback, isHomogeneous = false, callbackIsIndex } } + function recurseHomogeneous (value) { + if (Array.isArray(value[0])) { + value.forEach(recurseHomogeneous) + } else { + value.forEach(callback) + } + } + function recurseIndexedHeterogeneous (value, index, depth) { if (Array.isArray(value)) { value.forEach(function (child, i) { From 4a507e3a16becfc020cc50e340ae98849d8063f8 Mon Sep 17 00:00:00 2001 From: David Contreras Date: Mon, 3 Feb 2025 17:33:32 -0600 Subject: [PATCH 15/19] refactor deepMap to simplify zero-skipping logic and utilize iterableMap --- src/utils/collection.js | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/src/utils/collection.js b/src/utils/collection.js index 062455d2eb..74c2fd28b4 100644 --- a/src/utils/collection.js +++ b/src/utils/collection.js @@ -2,7 +2,7 @@ import { isCollection, isMatrix } from './is.js' import { IndexError } from '../error/IndexError.js' import { arraySize, findFirst as arrayFindFirst } from './array.js' import { _switch } from './switch.js' -import { forEach as iterableForEach } from './iterable.js' +import { forEach as iterableForEach, map as iterableMap } from './iterable.js' /** * Test whether an array contains collections @@ -56,28 +56,11 @@ export function deepForEach (array, callback) { * @return {Array | Matrix} res */ export function deepMap (array, callback, skipZeros) { - if (skipZeros) { - const callbackSkip = (x) => x === 0 ? 0 : callback(x) - if (isMatrix(array)) { - return array.create(recurse(array.valueOf(), callbackSkip), array.datatype()) - } else { - return recurse(array, callbackSkip) - } + const callbackSkip = skipZeros ? x => x === 0 ? 0 : callback(x) : x => callback(x) + if (isMatrix(array)) { + return array.map(callbackSkip) } else { - if (isMatrix(array)) { - return array.create(recurse(array.valueOf(), callback), array.datatype()) - } else { - return recurse(array, callback) - } - } - function recurse (array) { - if (Array.isArray(array)) { - return array.map(function (x) { - return recurse(x) - }) - } else { - return callback(array) - } + return iterableMap(array, callbackSkip, false, false, array) } } From 5eaa11e8120253f2b27e8b075e9fbe0dcbd92cd9 Mon Sep 17 00:00:00 2001 From: David Contreras Date: Mon, 3 Feb 2025 21:42:15 -0600 Subject: [PATCH 16/19] fix: add input validation to map and forEach functions --- src/utils/iterable.js | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/utils/iterable.js b/src/utils/iterable.js index 12b6b0097c..2318d5634d 100644 --- a/src/utils/iterable.js +++ b/src/utils/iterable.js @@ -9,22 +9,20 @@ * @returns {Array} The new array with the results of the callback function. */ export function map (array, callback, isHomogeneous = false, callbackIsIndexed = true, thisArg) { + if (!Array.isArray(array)) throw new TypeError('First argument to map must be an array') + if (typeof callback !== 'function') throw new TypeError('Second argument to map must be a function') + if (array.length === 0) return [] if (callbackIsIndexed) { thisArg = thisArg || array if (isHomogeneous) { - return array.map((value, index) => recurseIndexedHomogeneous(value, [index], 1)) + return recurseIndexedHomogeneous(array, [], 0) } else { return array.map((value, index) => recurseIndexedHeterogeneous(value, [index], 1)) } } else { + // not indexed if (isHomogeneous) { - return array.map(function rec (value) { - if (Array.isArray(value[0])) { - return value.map(rec) - } else { - return value.map(callback) - } - }) + return recurseHomogeneous(array) } else { return array.map(function rec (value) { if (Array.isArray(value)) { @@ -36,9 +34,17 @@ export function map (array, callback, isHomogeneous = false, callbackIsIndexed = } } + function recurseHomogeneous (value) { + if (Array.isArray(value[0])) { + return value.map(recurseHomogeneous) + } else { + return value.map(callback) + } + } + function recurseIndexedHeterogeneous (value, index, depth) { if (Array.isArray(value)) { - return value.map(function (child, i) { + return value.map((child, i) => { index[depth] = i const results = recurseIndexedHeterogeneous(child, index, depth + 1) index[depth] = null @@ -67,7 +73,7 @@ export function map (array, callback, isHomogeneous = false, callbackIsIndexed = } /** - * Applies a callback function to each element of the array using .forEach. + * Applies a callback function to each element of the array using. * * @param {Array} array - The array to be iterated over. * @param {Function|TypedFunction} callback - The function to execute on each element. @@ -76,6 +82,10 @@ export function map (array, callback, isHomogeneous = false, callbackIsIndexed = * @param {Object} [thisArg] - The value of this provided for the call to the callback function. */ export function forEach (array, callback, isHomogeneous = false, callbackIsIndexed = true, thisArg) { + if (!Array.isArray(array)) throw new TypeError('First argument to map must be an array') + if (typeof callback !== 'function') throw new TypeError('Second argument to map must be a function') + if (array.length === 0) return + if (callbackIsIndexed) { thisArg = thisArg || array if (isHomogeneous) { From aee64f708a99e505ef0b9c289542cce5dc7b57e5 Mon Sep 17 00:00:00 2001 From: David Contreras Date: Tue, 4 Feb 2025 20:21:50 -0600 Subject: [PATCH 17/19] refactor: simplify recursion logic in map and forEach functions --- src/utils/iterable.js | 56 ++++++++++++++++--------------------------- 1 file changed, 21 insertions(+), 35 deletions(-) diff --git a/src/utils/iterable.js b/src/utils/iterable.js index 2318d5634d..a6de96057f 100644 --- a/src/utils/iterable.js +++ b/src/utils/iterable.js @@ -14,19 +14,19 @@ export function map (array, callback, isHomogeneous = false, callbackIsIndexed = if (array.length === 0) return [] if (callbackIsIndexed) { thisArg = thisArg || array - if (isHomogeneous) { - return recurseIndexedHomogeneous(array, [], 0) - } else { - return array.map((value, index) => recurseIndexedHeterogeneous(value, [index], 1)) - } + if (isHomogeneous) return recurseIndexedHomogeneous(array, [], 0) + else return array.map((value, index) => recurseIndexedHeterogeneous(value, [index], 1)) } else { // not indexed - if (isHomogeneous) { - return recurseHomogeneous(array) - } else { + if (isHomogeneous) return recurseHomogeneous(array) + else { return array.map(function rec (value) { if (Array.isArray(value)) { - return value.map(rec) + if (value.every(ele => !Array.isArray(ele))) { + return value.map(callback) + } else { + return value.map(rec) + } } else { return callback(value) } @@ -35,11 +35,8 @@ export function map (array, callback, isHomogeneous = false, callbackIsIndexed = } function recurseHomogeneous (value) { - if (Array.isArray(value[0])) { - return value.map(recurseHomogeneous) - } else { - return value.map(callback) - } + if (Array.isArray(value[0])) return value.map(recurseHomogeneous) + else return value.map(callback) } function recurseIndexedHeterogeneous (value, index, depth) { @@ -50,9 +47,7 @@ export function map (array, callback, isHomogeneous = false, callbackIsIndexed = index[depth] = null return results }) - } else { - return callback(value, index.slice(), thisArg) - } + } else return callback(value, index.slice(0, depth + 1), thisArg) } function recurseIndexedHomogeneous (value, index, depth) { @@ -66,7 +61,7 @@ export function map (array, callback, isHomogeneous = false, callbackIsIndexed = } else { return value.map((v, i) => { index[depth] = i - return callback(v, index.slice(), thisArg) + return callback(v, index.slice(0, depth + 1), thisArg) }) } } @@ -88,15 +83,11 @@ export function forEach (array, callback, isHomogeneous = false, callbackIsIndex if (callbackIsIndexed) { thisArg = thisArg || array - if (isHomogeneous) { - array.forEach((value, index) => recurseIndexedHomogeneous(value, [index], 1)) - } else { - array.forEach((value, index) => recurseIndexedHeterogeneous(value, [index], 1)) - } + if (isHomogeneous) recurseIndexedHomogeneous(array, [], 0) + else array.forEach((value, index) => recurseIndexedHeterogeneous(value, [index], 1)) } else { - if (isHomogeneous) { - recurseHomogeneous(array) - } else { + if (isHomogeneous) recurseHomogeneous(array) + else { array.forEach(function rec (value) { if (Array.isArray(value)) { value.forEach(rec) @@ -108,11 +99,8 @@ export function forEach (array, callback, isHomogeneous = false, callbackIsIndex } function recurseHomogeneous (value) { - if (Array.isArray(value[0])) { - value.forEach(recurseHomogeneous) - } else { - value.forEach(callback) - } + if (Array.isArray(value[0])) value.forEach(recurseHomogeneous) + else value.forEach(callback) } function recurseIndexedHeterogeneous (value, index, depth) { @@ -122,9 +110,7 @@ export function forEach (array, callback, isHomogeneous = false, callbackIsIndex recurseIndexedHeterogeneous(child, index, depth + 1) index[depth] = null }) - } else { - callback(value, index.slice(), thisArg) - } + } else callback(value, index.slice(0, depth + 1), thisArg) } function recurseIndexedHomogeneous (value, index, depth) { @@ -137,7 +123,7 @@ export function forEach (array, callback, isHomogeneous = false, callbackIsIndex } else { value.forEach((v, i) => { index[depth] = i - callback(v, index.slice(), thisArg) + callback(v, index.slice(0, depth + 1), thisArg) }) } } From 66cdc32eab7101b0afd3bced0e114ce99a16c548 Mon Sep 17 00:00:00 2001 From: David Contreras Date: Tue, 4 Feb 2025 20:51:47 -0600 Subject: [PATCH 18/19] fix: add handling for empty matrix and array cases in findFirstValueAndIndex function --- src/utils/optimizeCallback.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/utils/optimizeCallback.js b/src/utils/optimizeCallback.js index 033a875d56..ede6de5cb6 100644 --- a/src/utils/optimizeCallback.js +++ b/src/utils/optimizeCallback.js @@ -42,6 +42,9 @@ export function optimizeCallback (callback, array, name, options) { } function findFirstValueAndIndex (array) { + if((array.isMatrix && array.valueOf().length === 0) || (Array.isArray(array) && array.length === 0)) { + return [undefined, []] + } const firstIndex = (array.isMatrix ? array.size() : arraySize(array)).map(() => 0) const firstValue = array.isMatrix ? array.get(firstIndex) : get(array, firstIndex) From 832e91e60ce53b512f0381f9e452791f39184bc7 Mon Sep 17 00:00:00 2001 From: David Contreras Date: Tue, 4 Feb 2025 21:13:01 -0600 Subject: [PATCH 19/19] fix: improve findFirstValueAndIndex function --- src/utils/optimizeCallback.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/utils/optimizeCallback.js b/src/utils/optimizeCallback.js index ede6de5cb6..17947c48a4 100644 --- a/src/utils/optimizeCallback.js +++ b/src/utils/optimizeCallback.js @@ -1,5 +1,5 @@ import typed from 'typed-function' -import { get, arraySize } from './array.js' +import { findFirst } from './array.js' import { typeOf as _typeOf } from './is.js' /** @@ -42,13 +42,16 @@ export function optimizeCallback (callback, array, name, options) { } function findFirstValueAndIndex (array) { - if((array.isMatrix && array.valueOf().length === 0) || (Array.isArray(array) && array.length === 0)) { - return [undefined, []] + if (array.isMatrix) { + if (array.valueOf().length === 0) return [undefined, []] + const firstIndex = array.size().map(() => 0) + const firstValue = array.get(firstIndex) + return [firstValue, firstIndex] + } else { + if (array.length === 0) return [undefined, []] + const { value, index } = findFirst(array) + return [value, index] } - const firstIndex = (array.isMatrix ? array.size() : arraySize(array)).map(() => 0) - const firstValue = array.isMatrix ? array.get(firstIndex) : get(array, firstIndex) - - return [firstValue, firstIndex] } export function findNumberOfArguments (callback, array) {