Skip to content

Commit

Permalink
feat: speed up the map() and forEach() functions in DenseMatrix.js (
Browse files Browse the repository at this point in the history
#3251)

* Optimize the map and forEach functions in DenseMatrix.js

* Changed index back to Array from Uint32Array and clone it using index.slice(0) instead of [...index]

* Fixed merge conflicts with the fast callback optimization

* Fixed the documentation for _forEach()

* Fixed _forEach comment and made it return an immutable index array

* Resolved DenseMatrix unit test suggestions

---------

Co-authored-by: Jos de Jong <[email protected]>
  • Loading branch information
Galm007 and josdejong authored Sep 27, 2024
1 parent fb76ac4 commit 7f810d1
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 20 deletions.
2 changes: 2 additions & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,9 @@ Adam Jones <[email protected]>
Lucas Eng <[email protected]>
Orel Ben Neriah <[email protected]>
Vistinum <[email protected]>
Nyaaboron <[email protected]>
Vas Sudanagunta <[email protected]>
Brooks Smith <[email protected]>
Jmar L. Pineda <[email protected]>

# Generated by tools/update-authors.js
87 changes: 75 additions & 12 deletions src/type/matrix/DenseMatrix.js
Original file line number Diff line number Diff line change
@@ -1,5 +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'
Expand Down Expand Up @@ -524,6 +525,69 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies
return this._size.slice(0) // return a clone of _size
}

/**
* 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: an array, an integer index to that
* array, and the Matrix being traversed.
*/
DenseMatrix.prototype._forEach = function (callback) {
// 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++) {
callback(me._data, i, [i])
}
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
callback(data[last], j, index.slice(0))
}
break
}

if (i === -1) {
break
}
}
}

/**
* Create a new matrix with the results of the callback function executed on
* each entry of the matrix.
Expand All @@ -535,17 +599,15 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies
* @return {DenseMatrix} matrix
*/
DenseMatrix.prototype.map = function (callback) {
// matrix instance
const me = this
const result = new DenseMatrix(me)
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 = recurse(me._data, [], me, fastCallback)
const datatype = me._datatype !== undefined
? getArrayDataType(data, typeOf)
: undefined
return new DenseMatrix(data, datatype)
result._forEach(function (arr, i, index) {
arr[i] = fastCallback(arr[i], index, me)
})

return result
}

/**
Expand All @@ -556,10 +618,11 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies
* of the element, and the Matrix being traversed.
*/
DenseMatrix.prototype.forEach = function (callback) {
// matrix instance
const me = this
const fastCallback = optimizeCallback(callback, me._data, 'forEach')
recurse(this._data, [], me, fastCallback)
const fastCallback = optimizeCallback(callback, me._data, 'map')
me._forEach(function (arr, i, index) {
fastCallback(arr[i], index, me)
})
}

/**
Expand Down
16 changes: 8 additions & 8 deletions test/unit-tests/type/matrix/DenseMatrix.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -734,22 +734,22 @@ 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]
}
)

assert.deepStrictEqual(
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]
]
])
})
Expand Down Expand Up @@ -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, [
Expand Down

0 comments on commit 7f810d1

Please sign in to comment.