From ed32de5ea4cb8758efd4016eba61afb7affd1097 Mon Sep 17 00:00:00 2001 From: Nyaaboron Date: Wed, 14 Aug 2024 11:32:36 -0400 Subject: [PATCH 1/6] Optimize the map and forEach functions in DenseMatrix.js --- src/type/matrix/DenseMatrix.js | 128 ++++++++++++++---- .../type/matrix/DenseMatrix.test.js | 12 +- .../type/matrix/ImmutableDenseMatrix.test.js | 12 +- 3 files changed, 117 insertions(+), 35 deletions(-) diff --git a/src/type/matrix/DenseMatrix.js b/src/type/matrix/DenseMatrix.js index 9bea6d6cf5..19bd284369 100644 --- a/src/type/matrix/DenseMatrix.js +++ b/src/type/matrix/DenseMatrix.js @@ -550,24 +550,65 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies DenseMatrix.prototype.map = function (callback) { // matrix instance const me = this - const recurse = function (value, index) { - if (isArray(value)) { - return value.map(function (child, i) { - return recurse(child, index.concat(i)) - }) - } else { - // invoke the callback function with the right number of arguments - return applyCallback(callback, value, index, me, 'map') + const s = me.size() + + // copy to a new matrix + const result = new DenseMatrix(me) + if (s.length <= 0) { + return result + } + + // keep track of the current index permutation + const index = new Uint32Array(s.length) + + // if there is only one dimension, just loop through it + if (s.length === 1) { + for (let i = 0; i < s[0]; i++) { + index[0] = i + result._data[i] = applyCallback(callback, result._data[i], index, me, 'map') } + return result } - // determine the new datatype when the original matrix has datatype defined - // TODO: should be done in matrix constructor instead - const data = recurse(this._data, []) - const datatype = this._datatype !== undefined - ? getArrayDataType(data, typeOf) - : undefined - return new DenseMatrix(data, datatype) + // stores a reference of each dimension of the matrix for faster access + const data = Array(s.length - 1) + const last = data.length - 1 + data[0] = result._data[0] + for (let i = 0; i < last; i++) { + data[i + 1] = data[i][0] + } + + index[last] = -1 + while (true) { + let i + for (i = last; i >= 0; i--) { + // march index to the next permutation + index[i]++ + if (index[i] === s[i]) { + index[i] = 0 + continue + } + + // update references to matrix dimensions + data[i] = i === 0 ? result._data[index[i]] : data[i - 1][index[i]] + for (let j = i; j < last; j++) { + data[j + 1] = data[j][0] + } + + // loop through the last dimension and map each value + for (let j = 0; j < s[data.length]; j++) { + index[data.length] = j + data[last][j] = applyCallback(callback, data[last][j], index, me, 'map') + } + break + } + + if (i === -1) { + break + } + } + + return result } /** @@ -580,16 +621,57 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies DenseMatrix.prototype.forEach = function (callback) { // matrix instance const me = this - const recurse = function (value, index) { - if (isArray(value)) { - value.forEach(function (child, i) { - recurse(child, index.concat(i)) - }) - } else { - callback(value, index, me) + const s = me.size() + + // if there is only one dimension, just loop through it + if (s.length === 1) { + for (let i = 0; i < s[0]; i++) { + applyCallback(callback, me._data[i], [i], me, 'forEach') + } + return + } + + // keep track of the current index permutation + const index = new Uint32Array(s.length) + + // store a reference of each dimension of the matrix for faster access + const data = Array(s.length - 1) + const last = data.length - 1 + + data[0] = me._data[0] + for (let i = 0; i < last; i++) { + data[i + 1] = data[i][0] + } + + index[last] = -1 + while (true) { + let i + for (i = last; i >= 0; i--) { + // march index to the next permutation + index[i]++ + if (index[i] === s[i]) { + index[i] = 0 + continue + } + + // update references to matrix dimensions + data[i] = i === 0 ? me._data[index[i]] : data[i - 1][index[i]] + for (let j = i; j < last; j++) { + data[j + 1] = data[j][0] + } + + // loop through the last dimension and map each value + for (let j = 0; j < s[data.length]; j++) { + index[data.length] = j + applyCallback(callback, data[last][j], [...index], me, 'forEach') + } + break + } + + if (i === -1) { + break } } - recurse(this._data, []) } /** diff --git a/test/unit-tests/type/matrix/DenseMatrix.test.js b/test/unit-tests/type/matrix/DenseMatrix.test.js index 679ba465e0..550c333f1a 100644 --- a/test/unit-tests/type/matrix/DenseMatrix.test.js +++ b/test/unit-tests/type/matrix/DenseMatrix.test.js @@ -742,14 +742,14 @@ describe('DenseMatrix', function () { m2.toArray(), [ [ - '[1,[0,0],true]', - '[2,[0,1],true]', - '[3,[0,2],true]' + '[1,{"0":0,"1":0},true]', + '[2,{"0":0,"1":1},true]', + '[3,{"0":0,"1":2},true]' ], [ - '[4,[1,0],true]', - '[5,[1,1],true]', - '[6,[1,2],true]' + '[4,{"0":1,"1":0},true]', + '[5,{"0":1,"1":1},true]', + '[6,{"0":1,"1":2},true]' ] ]) }) diff --git a/test/unit-tests/type/matrix/ImmutableDenseMatrix.test.js b/test/unit-tests/type/matrix/ImmutableDenseMatrix.test.js index db910c47fd..6db4f43b60 100644 --- a/test/unit-tests/type/matrix/ImmutableDenseMatrix.test.js +++ b/test/unit-tests/type/matrix/ImmutableDenseMatrix.test.js @@ -357,14 +357,14 @@ describe('ImmutableDenseMatrix', function () { m2.toArray(), [ [ - '[1,[0,0],true]', - '[2,[0,1],true]', - '[3,[0,2],true]' + '[1,{"0":0,"1":0},true]', + '[2,{"0":0,"1":1},true]', + '[3,{"0":0,"1":2},true]' ], [ - '[4,[1,0],true]', - '[5,[1,1],true]', - '[6,[1,2],true]' + '[4,{"0":1,"1":0},true]', + '[5,{"0":1,"1":1},true]', + '[6,{"0":1,"1":2},true]' ] ]) }) From 1c4fc114155d6601e3305e75b99075c35674b276 Mon Sep 17 00:00:00 2001 From: Nyaaboron Date: Thu, 29 Aug 2024 13:55:22 -0400 Subject: [PATCH 2/6] Changed index back to Array from Uint32Array and clone it using index.slice(0) instead of [...index] --- AUTHORS | 4 ++++ src/type/matrix/DenseMatrix.js | 7 ++++--- test/unit-tests/type/matrix/DenseMatrix.test.js | 12 ++++++------ .../type/matrix/ImmutableDenseMatrix.test.js | 12 ++++++------ 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/AUTHORS b/AUTHORS index 7f00f1bede..954a51e028 100644 --- a/AUTHORS +++ b/AUTHORS @@ -250,5 +250,9 @@ Adam Jones Lucas Eng Orel Ben Neriah <77707952+orelbn@users.noreply.github.com> Vistinum +Nyaaboron +Vas Sudanagunta +Brooks Smith <42363318+smith120bh@users.noreply.github.com> +Jmar L. Pineda <63294460+Galm007@users.noreply.github.com> # Generated by tools/update-authors.js diff --git a/src/type/matrix/DenseMatrix.js b/src/type/matrix/DenseMatrix.js index 52409ec986..40ac0581ae 100644 --- a/src/type/matrix/DenseMatrix.js +++ b/src/type/matrix/DenseMatrix.js @@ -1,3 +1,4 @@ +// deno-lint-ignore-file no-this-alias import { isArray, isBigNumber, isCollection, isIndex, isMatrix, isNumber, isString, typeOf } from '../../utils/is.js' import { arraySize, getArrayDataType, processSizesWildcard, reshape, resize, unsqueeze, validate, validateIndex, broadcastTo, get } from '../../utils/array.js' import { format } from '../../utils/string.js' @@ -546,7 +547,7 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies } // keep track of the current index permutation - const index = new Uint32Array(s.length) + const index = Array(s.length).fill(0) // if there is only one dimension, just loop through it if (s.length === 1) { @@ -619,7 +620,7 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies } // keep track of the current index permutation - const index = new Uint32Array(s.length) + const index = Array(s.length).fill(0) // store a reference of each dimension of the matrix for faster access const data = Array(s.length - 1) @@ -650,7 +651,7 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies // loop through the last dimension and map each value for (let j = 0; j < s[data.length]; j++) { index[data.length] = j - applyCallback(callback, data[last][j], [...index], me, 'forEach') + applyCallback(callback, data[last][j], index.slice(0), me, 'forEach') } break } diff --git a/test/unit-tests/type/matrix/DenseMatrix.test.js b/test/unit-tests/type/matrix/DenseMatrix.test.js index 550c333f1a..679ba465e0 100644 --- a/test/unit-tests/type/matrix/DenseMatrix.test.js +++ b/test/unit-tests/type/matrix/DenseMatrix.test.js @@ -742,14 +742,14 @@ describe('DenseMatrix', function () { m2.toArray(), [ [ - '[1,{"0":0,"1":0},true]', - '[2,{"0":0,"1":1},true]', - '[3,{"0":0,"1":2},true]' + '[1,[0,0],true]', + '[2,[0,1],true]', + '[3,[0,2],true]' ], [ - '[4,{"0":1,"1":0},true]', - '[5,{"0":1,"1":1},true]', - '[6,{"0":1,"1":2},true]' + '[4,[1,0],true]', + '[5,[1,1],true]', + '[6,[1,2],true]' ] ]) }) diff --git a/test/unit-tests/type/matrix/ImmutableDenseMatrix.test.js b/test/unit-tests/type/matrix/ImmutableDenseMatrix.test.js index 6db4f43b60..db910c47fd 100644 --- a/test/unit-tests/type/matrix/ImmutableDenseMatrix.test.js +++ b/test/unit-tests/type/matrix/ImmutableDenseMatrix.test.js @@ -357,14 +357,14 @@ describe('ImmutableDenseMatrix', function () { m2.toArray(), [ [ - '[1,{"0":0,"1":0},true]', - '[2,{"0":0,"1":1},true]', - '[3,{"0":0,"1":2},true]' + '[1,[0,0],true]', + '[2,[0,1],true]', + '[3,[0,2],true]' ], [ - '[4,{"0":1,"1":0},true]', - '[5,{"0":1,"1":1},true]', - '[6,{"0":1,"1":2},true]' + '[4,[1,0],true]', + '[5,[1,1],true]', + '[6,[1,2],true]' ] ]) }) From 020d8738b671c042a3abf3c31fcd8b5b0b877296 Mon Sep 17 00:00:00 2001 From: Nyaaboron Date: Thu, 12 Sep 2024 21:18:27 -0400 Subject: [PATCH 3/6] Fixed merge conflicts with the fast callback optimization --- AUTHORS | 2 +- src/type/matrix/DenseMatrix.js | 115 +++++++++++---------------------- 2 files changed, 39 insertions(+), 78 deletions(-) diff --git a/AUTHORS b/AUTHORS index 0914d0c9f5..954a51e028 100644 --- a/AUTHORS +++ b/AUTHORS @@ -251,8 +251,8 @@ Lucas Eng Orel Ben Neriah <77707952+orelbn@users.noreply.github.com> Vistinum Nyaaboron -Jmar L. Pineda <63294460+Galm007@users.noreply.github.com> Vas Sudanagunta Brooks Smith <42363318+smith120bh@users.noreply.github.com> +Jmar L. Pineda <63294460+Galm007@users.noreply.github.com> # Generated by tools/update-authors.js diff --git a/src/type/matrix/DenseMatrix.js b/src/type/matrix/DenseMatrix.js index 44d7754f66..b6821d40a2 100644 --- a/src/type/matrix/DenseMatrix.js +++ b/src/type/matrix/DenseMatrix.js @@ -1,6 +1,6 @@ // deno-lint-ignore-file no-this-alias 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 } from '../../utils/array.js' import { format } from '../../utils/string.js' import { isInteger } from '../../utils/number.js' import { clone, deepStrictEqual } from '../../utils/object.js' @@ -526,44 +526,35 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies } /** - * Create a new matrix with the results of the callback function executed on - * each entry of the matrix. + * Applies a callback function to a reference to each element of the matrix * @memberof DenseMatrix * @param {Function} callback The callback function is invoked with three - * parameters: the value of the element, the index + * parameters: , the index * of the element, and the Matrix being traversed. * * @return {DenseMatrix} matrix */ - DenseMatrix.prototype.map = function (callback) { - const fastCallback = optimizeCallback(callback, me._data, 'map') - + DenseMatrix.prototype._forEach = function (callback) { // matrix instance const me = this const s = me.size() - // copy to a new matrix - const result = new DenseMatrix(me) - if (s.length <= 0) { - return result - } - - // keep track of the current index permutation - const index = Array(s.length).fill(0) - // if there is only one dimension, just loop through it if (s.length === 1) { for (let i = 0; i < s[0]; i++) { - index[0] = i - result._data[i] = applyCallback(fastCallback, result._data[i], index, me, 'map') + callback(me._data, i, [i]) } - return result + return } - // stores a reference of each dimension of the matrix for faster access + // keep track of the current index permutation + const index = Array(s.length).fill(0) + + // store a reference of each dimension of the matrix for faster access const data = Array(s.length - 1) const last = data.length - 1 - data[0] = result._data[0] + + data[0] = me._data[0] for (let i = 0; i < last; i++) { data[i + 1] = data[i][0] } @@ -580,7 +571,7 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies } // update references to matrix dimensions - data[i] = i === 0 ? result._data[index[i]] : data[i - 1][index[i]] + data[i] = i === 0 ? me._data[index[i]] : data[i - 1][index[i]] for (let j = i; j < last; j++) { data[j + 1] = data[j][0] } @@ -588,7 +579,7 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies // loop through the last dimension and map each value for (let j = 0; j < s[data.length]; j++) { index[data.length] = j - data[last][j] = applyCallback(fastCallback, data[last][j], index, me, 'map') + callback(data[last], j, index) } break } @@ -597,6 +588,26 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies break } } + } + + /** + * Create a new matrix with the results of the callback function executed on + * each entry of the matrix. + * @memberof DenseMatrix + * @param {Function} callback The callback function is invoked with three + * parameters: the value of the element, the index + * of the element, and the Matrix being traversed. + * + * @return {DenseMatrix} matrix + */ + DenseMatrix.prototype.map = function (callback) { + const me = this + const result = new DenseMatrix(me) + const fastCallback = optimizeCallback(callback, me._data, 'map') + + result._forEach(function (arr, i, index) { + arr[i] = fastCallback(arr[i], index, me) + }) return result } @@ -609,61 +620,11 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies * of the element, and the Matrix being traversed. */ DenseMatrix.prototype.forEach = function (callback) { - const fastCallback = optimizeCallback(callback, me._data, 'forEach') - - // matrix instance const me = this - const s = me.size() - - // if there is only one dimension, just loop through it - if (s.length === 1) { - for (let i = 0; i < s[0]; i++) { - applyCallback(fastCallback, me._data[i], [i], me, 'forEach') - } - return - } - - // keep track of the current index permutation - const index = Array(s.length).fill(0) - - // store a reference of each dimension of the matrix for faster access - const data = Array(s.length - 1) - const last = data.length - 1 - - data[0] = me._data[0] - for (let i = 0; i < last; i++) { - data[i + 1] = data[i][0] - } - - index[last] = -1 - while (true) { - let i - for (i = last; i >= 0; i--) { - // march index to the next permutation - index[i]++ - if (index[i] === s[i]) { - index[i] = 0 - continue - } - - // update references to matrix dimensions - data[i] = i === 0 ? me._data[index[i]] : data[i - 1][index[i]] - for (let j = i; j < last; j++) { - data[j + 1] = data[j][0] - } - - // loop through the last dimension and map each value - for (let j = 0; j < s[data.length]; j++) { - index[data.length] = j - applyCallback(fastCallback, data[last][j], index.slice(0), me, 'forEach') - } - break - } - - if (i === -1) { - break - } - } + const fastCallback = optimizeCallback(callback, me._data, 'map') + me._forEach(function (arr, i, index) { + fastCallback(arr[i], index.slice(0), me) + }) } /** From 5d3b40ed1f0082f53c5f19ea84710e1fa8a313d9 Mon Sep 17 00:00:00 2001 From: Nyaaboron Date: Thu, 12 Sep 2024 21:56:44 -0400 Subject: [PATCH 4/6] Fixed the documentation for _forEach() --- src/type/matrix/DenseMatrix.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/type/matrix/DenseMatrix.js b/src/type/matrix/DenseMatrix.js index b6821d40a2..768d2754d4 100644 --- a/src/type/matrix/DenseMatrix.js +++ b/src/type/matrix/DenseMatrix.js @@ -529,8 +529,8 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies * Applies a callback function to a reference to each element of the matrix * @memberof DenseMatrix * @param {Function} callback The callback function is invoked with three - * parameters: , the index - * of the element, and the Matrix being traversed. + * parameters: an array, an integer index to that + * array, and the Matrix being traversed. * * @return {DenseMatrix} matrix */ From c7803be5418dad08519e39376dce1b4b545e9a09 Mon Sep 17 00:00:00 2001 From: Nyaaboron Date: Fri, 27 Sep 2024 00:35:15 -0400 Subject: [PATCH 5/6] Fixed _forEach comment and made it return an immutable index array --- src/type/matrix/DenseMatrix.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/type/matrix/DenseMatrix.js b/src/type/matrix/DenseMatrix.js index 768d2754d4..1d9054f37b 100644 --- a/src/type/matrix/DenseMatrix.js +++ b/src/type/matrix/DenseMatrix.js @@ -531,8 +531,6 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies * @param {Function} callback The callback function is invoked with three * parameters: an array, an integer index to that * array, and the Matrix being traversed. - * - * @return {DenseMatrix} matrix */ DenseMatrix.prototype._forEach = function (callback) { // matrix instance @@ -579,7 +577,7 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies // loop through the last dimension and map each value for (let j = 0; j < s[data.length]; j++) { index[data.length] = j - callback(data[last], j, index) + callback(data[last], j, index.slice(0)) } break } @@ -623,7 +621,7 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies const me = this const fastCallback = optimizeCallback(callback, me._data, 'map') me._forEach(function (arr, i, index) { - fastCallback(arr[i], index.slice(0), me) + fastCallback(arr[i], index, me) }) } From 0983c9575f7f6b4f716d4086b3d6773e2c8a39a5 Mon Sep 17 00:00:00 2001 From: Nyaaboron Date: Fri, 27 Sep 2024 00:40:02 -0400 Subject: [PATCH 6/6] Resolved DenseMatrix unit test suggestions --- test/unit-tests/type/matrix/DenseMatrix.test.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/unit-tests/type/matrix/DenseMatrix.test.js b/test/unit-tests/type/matrix/DenseMatrix.test.js index 679ba465e0..10bdca50ab 100644 --- a/test/unit-tests/type/matrix/DenseMatrix.test.js +++ b/test/unit-tests/type/matrix/DenseMatrix.test.js @@ -734,7 +734,7 @@ describe('DenseMatrix', function () { const m = new DenseMatrix([[1, 2, 3], [4, 5, 6]]) const m2 = m.map( function (value, index, obj) { - return JSON.stringify([value, index, obj === m]) + return [value, index, obj === m] } ) @@ -742,14 +742,14 @@ describe('DenseMatrix', function () { m2.toArray(), [ [ - '[1,[0,0],true]', - '[2,[0,1],true]', - '[3,[0,2],true]' + [1, [0, 0], true], + [2, [0, 1], true], + [3, [0, 2], true] ], [ - '[4,[1,0],true]', - '[5,[1,1],true]', - '[6,[1,2],true]' + [4, [1, 0], true], + [5, [1, 1], true], + [6, [1, 2], true] ] ]) }) @@ -792,7 +792,7 @@ describe('DenseMatrix', function () { const output = [] m.forEach( function (value, index, obj) { - output.push(math.clone([value, index, obj === m])) + output.push([value, index, obj === m]) } ) assert.deepStrictEqual(output, [