VYPR
High severity8.8GHSA Advisory· Published May 7, 2026· Updated May 8, 2026

CVE-2026-41139

CVE-2026-41139

Description

Math.js is an extensive math library for JavaScript and Node.js. From version 13.1.0 to before version 15.2.0, arbitrary JavaScript can be executed via the expression parser of mathjs. This issue has been patched in version 15.2.0.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
mathjsnpm
>= 13.1.0, < 15.2.015.2.0

Affected products

2

Patches

2
0aee2f61866e

fix: two security vulnerabilities allowing execution of arbitrary JavaScript via the expression parser (#3656)

https://github.com/josdejong/mathjsJos de JongApr 7, 2026via ghsa
8 files changed · +233 89
  • src/utils/array.js+3 0 modified
    @@ -844,6 +844,9 @@ export function stretch (arrayToStretch, sizeToStretch, dimToStretch) {
     */
     export function get (array, index) {
       if (!Array.isArray(array)) { throw new Error('Array expected') }
    +  if (!Array.isArray(index)) {
    +    throw new Error('Array expected for index')
    +  }
       const size = arraySize(array)
       if (index.length !== size.length) { throw new DimensionError(index.length, size.length) }
       for (let x = 0; x < index.length; x++) { validateIndex(index[x], size[x]) }
    
  • src/utils/customs.js+61 61 modified
    @@ -1,79 +1,83 @@
     import { hasOwnProperty } from './object.js'
     
     /**
    - * Get a property of a plain object
    + * Get a property of a plain object or array
      * Throws an error in case the object is not a plain object or the
      * property is not defined on the object itself
      * @param {Object} object
      * @param {string} prop
      * @return {*} Returns the property value when safe
      */
    -function getSafeProperty (object, prop) {
    -  // only allow getting safe properties of a plain object
    -  if (isSafeProperty(object, prop)) {
    +export function getSafeProperty (object, prop) {
    +  if (isSafeObjectProperty(object, prop) || isSafeArrayProperty(object, prop)) {
         return object[prop]
       }
     
    -  if (typeof object[prop] === 'function' && isSafeMethod(object, prop)) {
    -    throw new Error('Cannot access method "' + prop + '" as a property')
    +  if (isSafeMethod(object, prop)) {
    +    throw new Error(`Cannot access method "${prop}" as a property`)
    +  }
    +
    +  if (object === null || object === undefined) {
    +    throw new TypeError(`Cannot access property "${prop}": object is ${object}`)
       }
     
       throw new Error('No access to property "' + prop + '"')
     }
     
     /**
    - * Set a property on a plain object.
    + * Set a property on a plain object or array.
      * Throws an error in case the object is not a plain object or the
      * property would override an inherited property like .constructor or .toString
      * @param {Object} object
      * @param {string} prop
      * @param {*} value
      * @return {*} Returns the value
      */
    -// TODO: merge this function into access.js?
    -function setSafeProperty (object, prop, value) {
    -  // only allow setting safe properties of a plain object
    -  if (isSafeProperty(object, prop)) {
    +export function setSafeProperty (object, prop, value) {
    +  if (isSafeObjectProperty(object, prop) || isSafeArrayProperty(object, prop)) {
         object[prop] = value
         return value
       }
     
    -  throw new Error('No access to property "' + prop + '"')
    +  throw new Error(`No access to property "${prop}"`)
     }
     
     /**
    - * Test whether a property is safe to use on an object or Array.
    - * For example .toString and .constructor are not safe
    - * @param {Object | Array} object
    + * Test whether a property is safe for reading and writing on an object
    + * For example .constructor and .__proto__ are not safe
    + * @param {Object} object
      * @param {string} prop
      * @return {boolean} Returns true when safe
      */
    -function isSafeProperty (object, prop) {
    -  if (!isPlainObject(object) && !Array.isArray(object)) {
    +export function isSafeObjectProperty (object, prop) {
    +  if (!isPlainObject(object)) {
         return false
       }
    -  // SAFE: whitelisted
    -  // e.g length
    -  if (hasOwnProperty(safeNativeProperties, prop)) {
    -    return true
    -  }
    -  // UNSAFE: inherited from Object prototype
    -  // e.g constructor
    -  if (prop in Object.prototype) {
    -    // 'in' is used instead of hasOwnProperty for nodejs v0.10
    -    // which is inconsistent on root prototypes. It is safe
    -    // here because Object.prototype is a root object
    -    return false
    -  }
    -  // UNSAFE: inherited from Function prototype
    -  // e.g call, apply
    -  if (prop in Function.prototype) {
    -    // 'in' is used instead of hasOwnProperty for nodejs v0.10
    -    // which is inconsistent on root prototypes. It is safe
    -    // here because Function.prototype is a root object
    +
    +  return !(prop in Object.prototype)
    +}
    +
    +/**
    + * Test whether a property is safe for reading and writing on an Array
    + * For example .__proto__ and .constructor are not safe
    + * @param {unknown} array
    + * @param {string | number} prop
    + * @return {boolean} Returns true when safe
    + */
    +export function isSafeArrayProperty (array, prop) {
    +  if (!Array.isArray(array)) {
         return false
       }
    -  return true
    +
    +  return (
    +    typeof prop === 'number' ||
    +    (typeof prop === 'string' && isInteger(prop)) ||
    +    prop === 'length'
    +  )
    +}
    +
    +function isInteger (prop) {
    +  return /^\d+$/.test(prop)
     }
     
     /**
    @@ -83,7 +87,7 @@ function isSafeProperty (object, prop) {
      * @param {string} method
      * @return {function} Returns the method when valid
      */
    -function getSafeMethod (object, method) {
    +export function getSafeMethod (object, method) {
       if (!isSafeMethod(object, method)) {
         throw new Error('No access to method "' + method + '"')
       }
    @@ -98,22 +102,32 @@ function getSafeMethod (object, method) {
      * @param {string} method
      * @return {boolean} Returns true when safe, false otherwise
      */
    -function isSafeMethod (object, method) {
    -  if (object === null || object === undefined || typeof object[method] !== 'function') {
    +export function isSafeMethod (object, method) {
    +  if (
    +    object === null ||
    +    object === undefined ||
    +    typeof object[method] !== 'function'
    +  ) {
         return false
       }
    +
       // UNSAFE: ghosted
       // e.g overridden toString
       // Note that IE10 doesn't support __proto__ and we can't do this check there.
    -  if (hasOwnProperty(object, method) &&
    -      (Object.getPrototypeOf && (method in Object.getPrototypeOf(object)))) {
    +  if (
    +    hasOwnProperty(object, method) &&
    +    Object.getPrototypeOf &&
    +    method in Object.getPrototypeOf(object)
    +  ) {
         return false
       }
    +
       // SAFE: whitelisted
       // e.g toString
    -  if (hasOwnProperty(safeNativeMethods, method)) {
    +  if (safeNativeMethods.has(method)) {
         return true
       }
    +
       // UNSAFE: inherited from Object prototype
       // e.g constructor
       if (method in Object.prototype) {
    @@ -122,6 +136,7 @@ function isSafeMethod (object, method) {
         // here because Object.prototype is a root object
         return false
       }
    +
       // UNSAFE: inherited from Function prototype
       // e.g call, apply
       if (method in Function.prototype) {
    @@ -130,27 +145,12 @@ function isSafeMethod (object, method) {
         // here because Function.prototype is a root object
         return false
       }
    +
       return true
     }
     
    -function isPlainObject (object) {
    +export function isPlainObject (object) {
       return typeof object === 'object' && object && object.constructor === Object
     }
     
    -const safeNativeProperties = {
    -  length: true,
    -  name: true
    -}
    -
    -const safeNativeMethods = {
    -  toString: true,
    -  valueOf: true,
    -  toLocaleString: true
    -}
    -
    -export { getSafeProperty }
    -export { setSafeProperty }
    -export { isSafeProperty }
    -export { getSafeMethod }
    -export { isSafeMethod }
    -export { isPlainObject }
    +const safeNativeMethods = new Set(['toString', 'valueOf', 'toLocaleString'])
    
  • src/utils/map.js+7 3 modified
    @@ -1,4 +1,8 @@
    -import { getSafeProperty, isSafeProperty, setSafeProperty } from './customs.js'
    +import {
    +  getSafeProperty,
    +  isSafeObjectProperty,
    +  setSafeProperty
    +} from './customs.js'
     import { isMap, isObject } from './is.js'
     
     /**
    @@ -32,7 +36,7 @@ export class ObjectWrappingMap {
       }
     
       has (key) {
    -    return isSafeProperty(this.wrappedObject, key) && key in this.wrappedObject
    +    return isSafeObjectProperty(this.wrappedObject, key) && key in this.wrappedObject
       }
     
       entries () {
    @@ -46,7 +50,7 @@ export class ObjectWrappingMap {
       }
     
       delete (key) {
    -    if (isSafeProperty(this.wrappedObject, key)) {
    +    if (isSafeObjectProperty(this.wrappedObject, key)) {
           delete this.wrappedObject[key]
         }
       }
    
  • test/unit-tests/expression/security.test.js+47 3 modified
    @@ -76,11 +76,55 @@ describe('security', function () {
       it('should not allow calling Function via Object.assign', function () {
         // TODO: simplify this test case, let it output console.log('hacked...')
         assert.throws(function () {
    -      math.evaluate('{}.constructor.assign(cos.constructor, {binding: cos.bind})\n' +
    +      math.evaluate(
    +        '{}.constructor.assign(cos.constructor, {binding: cos.bind})\n' +
               '{}.constructor.assign(cos.constructor, {bind: null})\n' +
               'f=cos.constructor.binding()("console.log(\'hacked...\')")\n' +
    -          'f()')
    -    }, /Error: No access to property "bind/)
    +          'f()'
    +      )
    +    }, /Error: No access to property "constructor/)
    +  })
    +
    +  it('should not allow misusing setSafeProperty to set properties on Arrays', function () {
    +    assert.throws(function () {
    +      const exploit = `
    +        constantNode = reviver('',{'mathjs':'ConstantNode','value':1});
    +        
    +        # get a reference to a raw JavaScript array
    +        array = reviver('',{'mathjs':'ArrayNode'}).map().toJSON()['items'];
    +        array.push({'name':'a','type':{}});
    +        
    +        # override params.map to hijack this.params and this.types
    +        array.map = f(callback)=callback({'name':{'name':'a','map':f2(callback2)=callback2({},'constructor')},'type':cos});
    +        
    +        # trigger callback and get the pointer to Function.constructor
    +        functionAssignmentNode = reviver('',{'mathjs':'FunctionAssignmentNode','name':'a','params':array,'expr':constantNode});
    +        func = functionAssignmentNode.toJSON()['params']['type'];
    +        
    +        getProcess = func('return process');
    +        getProcess()`
    +
    +      console.log(
    +        'Hacked! node.js version:',
    +        math.evaluate(exploit).entries[0].version
    +      )
    +    }, /Error: No access to property "map"/)
    +  })
    +
    +  it('should not allow getting access to constructor via DenseMatrix.get index argument', function () {
    +    assert.throws(function () {
    +      const exploit = `
    +        m = matrix();
    +        func = m.get({"length":1,"reduce":f(callback,a)=callback(cos,"constructor")});
    +        getProcess = func("return process");
    +        getProcess()
    +        `
    +
    +      console.log(
    +        'Hacked! node.js version:',
    +        math.evaluate(exploit).entries[0].version
    +      )
    +    }, /Error: Array expected for index/)
       })
     
       it('should not allow disguising forbidden properties with unicode characters', function () {
    
  • test/unit-tests/type/matrix/DenseMatrix.test.js+10 0 modified
    @@ -440,6 +440,16 @@ describe('DenseMatrix', function () {
           assert.throws(function () { m.get(math.index(1, 1)) })
           assert.throws(function () { m.get([[1, 1]]) })
         })
    +
    +    it('should throw an error when getting a value given an index that is not an array', function () {
    +      assert.throws(function () {
    +        m.get({ length: 1, reduce: () => {} })
    +      }, /Error: Array expected for index/)
    +
    +      assert.throws(function () {
    +        m.get(new Date())
    +      }, /Error: Array expected for index/)
    +    })
       })
     
       describe('set', function () {
    
  • test/unit-tests/type/matrix/SparseMatrix.test.js+15 0 modified
    @@ -730,6 +730,21 @@ describe('SparseMatrix', function () {
           assert.strictEqual(m.get([4, 5]), 13)
           assert.strictEqual(m.get([5, 5]), -1)
         })
    +
    +    it('should throw an error when getting a value given an index that is not an array', function () {
    +      const m = new SparseMatrix([
    +        [1, 2],
    +        [3, 4]
    +      ])
    +
    +      assert.throws(function () {
    +        m.get({ length: 1, reduce: () => {} })
    +      }, /Error: Array expected/)
    +
    +      assert.throws(function () {
    +        m.get(new Date())
    +      }, /Error: Array expected/)
    +    })
       })
     
       describe('set', function () {
    
  • test/unit-tests/utils/array.test.js+10 0 modified
    @@ -678,6 +678,16 @@ describe('util.array', function () {
           assert.throws(function () { get(m, null) })
           assert.throws(function () { get(m, [[1, 1]]) })
         })
    +
    +    it('should throw an error when getting a value given an index that is not an array', function () {
    +      assert.throws(function () {
    +        get(m, { length: 1, reduce: () => {} })
    +      }, /Error: Array expected for index/)
    +
    +      assert.throws(function () {
    +        get(m, new Date())
    +      }, /Error: Array expected for index/)
    +    })
       })
     
       describe('checkBroadcastingRules', function () {
    
  • test/unit-tests/utils/customs.test.js+80 22 modified
    @@ -1,14 +1,16 @@
     // test boolean utils
     import assert from 'assert'
    +import math from '../../../src/defaultInstance.js'
     
     import {
       getSafeMethod,
       getSafeProperty,
       isPlainObject,
    +  isSafeArrayProperty,
       isSafeMethod,
    -  isSafeProperty
    +  isSafeObjectProperty,
    +  setSafeProperty
     } from '../../../src/utils/customs.js'
    -import math from '../../../src/defaultInstance.js'
     
     describe('customs', function () {
       describe('isSafeMethod', function () {
    @@ -130,55 +132,63 @@ describe('customs', function () {
         })
       })
     
    -  describe('isSafeProperty', function () {
    +  describe('isSafeObjectProperty', function () {
         it('should test properties on plain objects', function () {
           const object = {}
     
           /* From Object.prototype:
             Object.getOwnPropertyNames(Object.prototype).forEach(
               key => typeof ({})[key] !== 'function' && console.log(key))
           */
    -      assert.strictEqual(isSafeProperty(object, '__proto__'), false)
    -      assert.strictEqual(isSafeProperty(object, 'constructor'), false)
    +      assert.strictEqual(isSafeObjectProperty(object, '__proto__'), false)
    +      assert.strictEqual(isSafeObjectProperty(object, 'constructor'), false)
     
           /* From Function.prototype:
             Object.getOwnPropertyNames(Function.prototype).forEach(
               key => typeof (function () {})[key] !== 'function' && console.log(key))
           */
    -      assert.strictEqual(isSafeProperty(object, 'length'), true)
    -      assert.strictEqual(isSafeProperty(object, 'name'), true)
    -      assert.strictEqual(isSafeProperty(object, 'arguments'), false)
    -      assert.strictEqual(isSafeProperty(object, 'caller'), false)
    +      assert.strictEqual(isSafeObjectProperty(object, 'length'), true)
    +      assert.strictEqual(isSafeObjectProperty(object, 'name'), true)
    +      // assert.strictEqual(isSafeObjectProperty(object, 'arguments'), false)
    +      // assert.strictEqual(isSafeObjectProperty(object, 'caller'), false)
     
           // non-existing property
    -      assert.strictEqual(isSafeProperty(object, 'bar'), true)
    +      assert.strictEqual(isSafeObjectProperty(object, 'bar'), true)
     
           // property with unicode chars
    -      assert.strictEqual(isSafeProperty(object, 'co\u006Estructor'), false)
    +      assert.strictEqual(
    +        isSafeObjectProperty(object, 'co\u006Estructor'),
    +        false
    +      )
         })
     
         it('should test inherited properties on plain objects', function () {
           const object1 = {}
           const object2 = Object.create(object1)
           object1.foo = true
           object2.bar = true
    -      assert.strictEqual(isSafeProperty(object2, 'foo'), true)
    -      assert.strictEqual(isSafeProperty(object2, 'bar'), true)
    -      assert.strictEqual(isSafeProperty(object2, '__proto__'), false)
    -      assert.strictEqual(isSafeProperty(object2, 'constructor'), false)
    +      assert.strictEqual(isSafeObjectProperty(object2, 'foo'), true)
    +      assert.strictEqual(isSafeObjectProperty(object2, 'bar'), true)
    +      assert.strictEqual(isSafeObjectProperty(object2, '__proto__'), false)
    +      assert.strictEqual(isSafeObjectProperty(object2, 'constructor'), false)
     
           object2.foo = true // override "foo" of object1
    -      assert.strictEqual(isSafeProperty(object2, 'foo'), true)
    -      assert.strictEqual(isSafeProperty(object2, 'constructor'), false)
    +      assert.strictEqual(isSafeObjectProperty(object2, 'foo'), true)
    +      assert.strictEqual(isSafeObjectProperty(object2, 'constructor'), false)
         })
    +  })
     
    +  describe('isSafeArrayProperty', function () {
         it('should test properties on an array', function () {
           const array = [3, 2, 1]
    -      assert.strictEqual(isSafeProperty(array, 'length'), true)
    -      assert.strictEqual(isSafeProperty(array, 'foo'), true)
    -      assert.strictEqual(isSafeProperty(array, 'sort'), true)
    -      assert.strictEqual(isSafeProperty(array, '__proto__'), false)
    -      assert.strictEqual(isSafeProperty(array, 'constructor'), false)
    +      assert.strictEqual(isSafeArrayProperty(array, 'length'), true)
    +      assert.strictEqual(isSafeArrayProperty(array, 42), true)
    +      assert.strictEqual(isSafeArrayProperty(array, '24'), true)
    +
    +      assert.strictEqual(isSafeArrayProperty(array, 'foo'), false)
    +      assert.strictEqual(isSafeArrayProperty(array, 'sort'), false)
    +      assert.strictEqual(isSafeArrayProperty(array, '__proto__'), false)
    +      assert.strictEqual(isSafeArrayProperty(array, 'constructor'), false)
         })
       })
     
    @@ -195,6 +205,19 @@ describe('customs', function () {
           assert.strictEqual(getSafeProperty(obj, 'username'), 'Joe')
         })
     
    +    it('should return a property from an array when safe', function () {
    +      const array = [3, 2, 1]
    +      array.foo = 42
    +      assert.strictEqual(getSafeProperty(array, 'length'), 3)
    +      assert.throws(() => getSafeProperty(array, 'foo'), /No access to property/)
    +      assert.throws(
    +        () => getSafeProperty(array, 'sort'),
    +        /Error: Cannot access method "sort" as a property/
    +      )
    +      assert.throws(() => getSafeProperty(array, '__proto__'), /No access to property/)
    +      assert.throws(() => getSafeProperty(array, 'constructor'), /No access to property "constructor"/)
    +    })
    +
         it('should throw an exception when a method is unsafe', function () {
           assert.throws(() => {
             getSafeProperty(Function, 'constructor')
    @@ -208,6 +231,41 @@ describe('customs', function () {
         })
       })
     
    +  describe('setSafeProperty', function () {
    +    it('should set a property on an object when safe', function () {
    +      const obj = {}
    +      setSafeProperty(obj, 'foo', 42)
    +      assert.deepStrictEqual(obj, { foo: 42 })
    +
    +      assert.throws(() => {
    +        setSafeProperty(obj, 'constructor', 42)
    +      }, /No access to property "constructor"/)
    +    })
    +
    +    it('should set a property on an object when safe (2)', function () {
    +      const obj = {}
    +      setSafeProperty(obj, '42', 'fortytwo')
    +      assert.deepStrictEqual(obj, { 42: 'fortytwo' })
    +    })
    +
    +    it('should set a property on an array when safe', function () {
    +      const arr = []
    +      setSafeProperty(arr, 0, 'zero')
    +      setSafeProperty(arr, '1', 'one')
    +      setSafeProperty(arr, '2', 'two')
    +      assert.deepStrictEqual(arr, ['zero', 'one', 'two'])
    +
    +      setSafeProperty(arr, 'length', 10)
    +      assert.deepStrictEqual(arr.length, 10)
    +    })
    +
    +    it('should not allow setting a non-numeric property on an array', function () {
    +      assert.throws(() => {
    +        setSafeProperty([], 'map', () => {})
    +      }, /No access to property "map"/)
    +    })
    +  })
    +
       it('should distinguish plain objects', function () {
         const a = {}
         const b = Object.create(a)
    
bcf0da46f0b8

feat: support multiple inputs in function `map` (#3228)

https://github.com/josdejong/mathjsDavid ContrerasAug 22, 2024via ghsa
12 files changed · +542 88
  • docs/datatypes/matrices.md+33 0 modified
    @@ -414,6 +414,39 @@ const cum = a.map(function (value, index, matrix) {
     console.log(cum.toString())  // [[0, 1], [3, 6], [10, 15]]
     ```
     
    +### Iterating over multiple Matrixes or Arrays
    +
    +You can iterate over multiple matrices or arrays by using the `map` function. Mapping allows to perform element-wise operations on matrices by automatically adjusting their sizes to match each other.
    +
    +To iterate over multiple matrices, you can use the `map` function. The `map` function applies a given function to each element of the matrices and returns a new matrix with the results.
    +
    +Here's an example of iterating over two matrices and adding their corresponding elements:
    +
    +```js
    +const a = math.matrix([[1, 2], [3, 4]]);
    +const b = math.matrix([[5, 6], [7, 8]]);
    +
    +const result = math.map(a, b, (x, y) => x + y);
    +
    +console.log(result); // [[6, 8], [10, 12]]
    +```
    +
    +In this example, the `map` function takes matrices as the first two arguments and a callback function `(x, y) => x + y` as the third argument. The callback function is applied to each element of the matrices, where `x` represents the corresponding element from matrix `a` and `y` represents the corresponding element from matrix `b`. The result is a new matrix with the element-wise sum of the two matrices.
    +
    +By using broadcasting and the `map` function, you can easily iterate over multiple matrices and perform element-wise operations.
    +
    +```js
    +const a = math.matrix([10, 20])
    +const b = math.matrix([[3, 4], [5, 6]])
    +
    +const result = math.map(a, b, (x, y) => x + y)
    +console.log(result); // [[13, 24], [15, 26]]
    +```
    +
    +It's also possible to provide a callback with an index and the broadcasted arrays. Like `(valueA, valueB, index)` or even `(valueA, valueB, index, broadcastedMatrixA, broadcastedMatrixB)`. There is no specific limit for the number of matrices `N` that can be mapped. Thus, the callback can have `N` arguments, `N+1` arguments in the case of including the index, or `2N+1` arguments in the case of including the index and the broadcasted matrices in the callback. 
    +
    +At this moment `forEach` doesn't include the same functionality.
    +
     ## Storage types
     
     Math.js supports both dense matrices as well as sparse matrices. Sparse matrices are efficient for matrices largely containing zeros. In that case they save a lot of memory, and calculations can be much faster than for dense matrices.
    
  • src/expression/embeddedDocs/function/matrix/map.js+5 3 modified
    @@ -2,11 +2,13 @@ export const mapDocs = {
       name: 'map',
       category: 'Matrix',
       syntax: [
    -    'map(x, callback)'
    +    'map(x, callback)',
    +    'map(x, y, ..., callback)'
       ],
    -  description: 'Create a new matrix or array with the results of the callback function executed on each entry of the matrix/array.',
    +  description: 'Create a new matrix or array with the results of the callback function executed on each entry of the matrix/array or the matrices/arrays.',
       examples: [
    -    'map([1, 2, 3], square)'
    +    'map([1, 2, 3], square)',
    +    'map([1, 2], [3, 4], f(a,b) = a + b)'
       ],
       seealso: ['filter', 'forEach']
     }
    
  • src/expression/transform/map.transform.js+97 36 modified
    @@ -1,7 +1,6 @@
    -import { applyCallback } from '../../utils/applyCallback.js'
    -import { map } from '../../utils/array.js'
     import { factory } from '../../utils/factory.js'
     import { isFunctionAssignmentNode, isSymbolNode } from '../../utils/is.js'
    +import { createMap } from '../../function/matrix/map.js'
     import { compileInlineExpression } from './utils/compileInlineExpression.js'
     
     const name = 'map'
    @@ -14,61 +13,123 @@ export const createMapTransform = /* #__PURE__ */ factory(name, dependencies, ({
        *
        * This transform creates a one-based index instead of a zero-based index
        */
    +  const map = createMap({ typed })
    +
       function mapTransform (args, math, scope) {
    -    let x, callback
    +    if (args.length === 0) {
    +      return map()
    +    }
     
    -    if (args[0]) {
    -      x = args[0].compile().evaluate(scope)
    +    if (args.length === 1) {
    +      return map(args[0])
         }
    +    const N = args.length - 1
    +    let X, callback
    +    callback = args[N]
    +    X = args.slice(0, N)
    +    X = X.map(arg => _compileAndEvaluate(arg, scope))
     
    -    if (args[1]) {
    -      if (isSymbolNode(args[1]) || isFunctionAssignmentNode(args[1])) {
    +    if (callback) {
    +      if (isSymbolNode(callback) || isFunctionAssignmentNode(callback)) {
             // a function pointer, like filter([3, -2, 5], myTestFunction)
    -        callback = args[1].compile().evaluate(scope)
    +        callback = _compileAndEvaluate(callback, scope)
           } else {
             // an expression like filter([3, -2, 5], x > 0)
    -        callback = compileInlineExpression(args[1], math, scope)
    +        callback = compileInlineExpression(callback, math, scope)
           }
         }
    +    return map(...X, _transformCallback(callback, N))
     
    -    return map(x, callback)
    +    function _compileAndEvaluate (arg, scope) {
    +      return arg.compile().evaluate(scope)
    +    }
       }
       mapTransform.rawArgs = true
     
    -  // one-based version of map function
    -  const map = typed('map', {
    -    'Array, function': function (x, callback) {
    -      return _map(x, callback, x)
    -    },
    +  return mapTransform
     
    -    'Matrix, function': function (x, callback) {
    -      return x.create(_map(x.valueOf(), callback, x), x.datatype())
    +  /**
    +   * Transforms the given callback function based on its type and number of arrays.
    +   *
    +   * @param {Function} callback - The callback function to transform.
    +   * @param {number} numberOfArrays - The number of arrays to pass to the callback function.
    +   * @returns {*} - The transformed callback function.
    +   */
    +  function _transformCallback (callback, numberOfArrays) {
    +    if (typed.isTypedFunction(callback)) {
    +      return _transformTypedCallbackFunction(callback, numberOfArrays)
    +    } else {
    +      return _transformCallbackFunction(callback, callback.length, numberOfArrays)
         }
    -  })
    +  }
     
    -  return mapTransform
    +  /**
    +   * Transforms the given typed callback function based on the number of arrays.
    +   *
    +   * @param {Function} typedFunction - The typed callback function to transform.
    +   * @param {number} numberOfArrays - The number of arrays to pass to the callback function.
    +   * @returns {*} - The transformed typed callback function.
    +   */
    +  function _transformTypedCallbackFunction (typedFunction, numberOfArrays) {
    +    const signatures = Object.fromEntries(
    +      Object.entries(typedFunction.signatures)
    +        .map(([signature, callbackFunction]) => {
    +          const numberOfCallbackInputs = signature.split(',').length
    +          if (typed.isTypedFunction(callbackFunction)) {
    +            return [signature, _transformTypedCallbackFunction(callbackFunction, numberOfArrays)]
    +          } else {
    +            return [signature, _transformCallbackFunction(callbackFunction, numberOfCallbackInputs, numberOfArrays)]
    +          }
    +        })
    +    )
    +
    +    if (typeof typedFunction.name === 'string') {
    +      return typed(typedFunction.name, signatures)
    +    } else {
    +      return typed(signatures)
    +    }
    +  }
     }, { isTransformFunction: true })
     
     /**
    - * Map for a multidimensional array. One-based indexes
    - * @param {Array} array
    - * @param {function} callback
    - * @param {Array} orig
    - * @return {Array}
    - * @private
    + * Transforms the callback function based on the number of callback inputs and arrays.
    + * There are three cases:
    + * 1. The callback function has N arguments.
    + * 2. The callback function has N+1 arguments.
    + * 3. The callback function has 2N+1 arguments.
    + *
    + * @param {Function} callbackFunction - The callback function to transform.
    + * @param {number} numberOfCallbackInputs - The number of callback inputs.
    + * @param {number} numberOfArrays - The number of arrays.
    + * @returns {Function} The transformed callback function.
      */
    -function _map (array, callback, orig) {
    -  function recurse (value, index) {
    -    if (Array.isArray(value)) {
    -      return map(value, function (child, i) {
    -        // we create a copy of the index array and append the new index value
    -        return recurse(child, index.concat(i + 1)) // one based index, hence i + 1
    -      })
    -    } else {
    -      // invoke the (typed) callback function with the right number of arguments
    -      return applyCallback(callback, value, index, orig, 'map')
    +function _transformCallbackFunction (callbackFunction, numberOfCallbackInputs, numberOfArrays) {
    +  if (numberOfCallbackInputs === numberOfArrays) {
    +    return callbackFunction
    +  } else if (numberOfCallbackInputs === numberOfArrays + 1) {
    +    return function (...args) {
    +      const vals = args.slice(0, numberOfArrays)
    +      const idx = _transformDims(args[numberOfArrays])
    +      return callbackFunction(...vals, idx)
    +    }
    +  } else if (numberOfCallbackInputs > numberOfArrays + 1) {
    +    return function (...args) {
    +      const vals = args.slice(0, numberOfArrays)
    +      const idx = _transformDims(args[numberOfArrays])
    +      const rest = args.slice(numberOfArrays + 1)
    +      return callbackFunction(...vals, idx, ...rest)
         }
    +  } else {
    +    return callbackFunction
       }
    +}
     
    -  return recurse(array, [])
    +/**
    + * Transforms the dimensions by adding 1 to each dimension.
    + *
    + * @param {Array} dims - The dimensions to transform.
    + * @returns {Array} The transformed dimensions.
    + */
    +function _transformDims (dims) {
    +  return dims.map(dim => dim.isBigNumber ? dim.plus(1) : dim + 1)
     }
    
  • src/expression/transform/utils/dimToZeroBase.js+17 0 added
    @@ -0,0 +1,17 @@
    +import { isNumber, isBigNumber } from '../../../utils/is.js'
    +/**
    + * Change last argument dim from one-based to zero-based.
    + */
    +export function dimToZeroBase (dim) {
    +  if (isNumber(dim)) {
    +    return dim - 1
    +  } else if (isBigNumber(dim)) {
    +    return dim.minus(1)
    +  } else {
    +    return dim
    +  }
    +}
    +
    +export function isNumberOrBigNumber (n) {
    +  return isNumber(n) || isBigNumber(n)
    +}
    
  • src/expression/transform/utils/lastDimToZeroBase.js+4 6 modified
    @@ -1,16 +1,14 @@
    -import { isBigNumber, isCollection, isNumber } from '../../../utils/is.js'
    -
    +import { isCollection } from '../../../utils/is.js'
    +import { dimToZeroBase, isNumberOrBigNumber } from './dimToZeroBase.js'
     /**
      * Change last argument dim from one-based to zero-based.
      */
     export function lastDimToZeroBase (args) {
       if (args.length === 2 && isCollection(args[0])) {
         args = args.slice()
         const dim = args[1]
    -    if (isNumber(dim)) {
    -      args[1] = dim - 1
    -    } else if (isBigNumber(dim)) {
    -      args[1] = dim.minus(1)
    +    if (isNumberOrBigNumber(dim)) {
    +      args[1] = dimToZeroBase(dim)
         }
       }
       return args
    
  • src/function/matrix/flatten.js+5 5 modified
    @@ -2,9 +2,9 @@ import { flatten as flattenArray } from '../../utils/array.js'
     import { factory } from '../../utils/factory.js'
     
     const name = 'flatten'
    -const dependencies = ['typed', 'matrix']
    +const dependencies = ['typed']
     
    -export const createFlatten = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix }) => {
    +export const createFlatten = /* #__PURE__ */ factory(name, dependencies, ({ typed }) => {
       /**
        * Flatten a multidimensional matrix into a single dimensional matrix.
        * A new matrix is returned, the original matrix is left untouched.
    @@ -30,9 +30,9 @@ export const createFlatten = /* #__PURE__ */ factory(name, dependencies, ({ type
         },
     
         Matrix: function (x) {
    -      const flat = flattenArray(x.toArray())
    -      // TODO: return the same matrix type as x (Dense or Sparse Matrix)
    -      return matrix(flat)
    +      // Return the same matrix type as x (Dense or Sparse Matrix)
    +      // Return the same data type as x
    +      return x.create(flattenArray(x.toArray()), x.datatype())
         }
       })
     })
    
  • src/function/matrix/map.js+120 16 modified
    @@ -1,4 +1,5 @@
     import { applyCallback } from '../../utils/applyCallback.js'
    +import { arraySize, broadcastSizes, broadcastTo, get } from '../../utils/array.js'
     import { factory } from '../../utils/factory.js'
     
     const name = 'map'
    @@ -9,27 +10,36 @@ export const createMap = /* #__PURE__ */ factory(name, dependencies, ({ typed })
        * Create a new matrix or array with the results of a callback function executed on
        * each entry of a given matrix/array.
        *
    -   * For each entry of the input, the callback is invoked with three arguments:
    -   * the value of the entry, the index at which that entry occurs, and the full
    -   * matrix/array being traversed. Note that because the matrix/array might be
    +   * For each entry of the input,
    +   *
    +   * the callback is invoked with 2N + 1 arguments:
    +   * the N values of the entry, the index at which that entry occurs, and the N full
    +   * broadcasted matrix/array being traversed where N is the number of matrices being traversed.
    +   * Note that because the matrix/array might be
        * multidimensional, the "index" argument is always an array of numbers giving
        * the index in each dimension. This is true even for vectors: the "index"
        * argument is an array of length 1, rather than simply a number.
        *
        * Syntax:
        *
        *    math.map(x, callback)
    +   *    math.map(x, y, ..., callback)
        *
        * Examples:
        *
        *    math.map([1, 2, 3], function(value) {
        *      return value * value
        *    })  // returns [1, 4, 9]
    +   *    math.map([1, 2], [3, 4], function(a, b) {
    +   *     return a + b
    +   *    })  // returns [4, 6]
        *
        *    // The callback is normally called with three arguments:
        *    //    callback(value, index, Array)
        *    // If you want to call with only one argument, use:
        *    math.map([1, 2, 3], x => math.format(x)) // returns ['1', '2', '3']
    +   *    // It can also be called with 2N + 1 arguments: for N arrays
    +   *    //    callback(value1, value2, index, BroadcastedArray1, BroadcastedArray2)
        *
        * See also:
        *
    @@ -42,12 +52,97 @@ export const createMap = /* #__PURE__ */ factory(name, dependencies, ({ typed })
        *     Transformed map of x; always has the same type and shape as x
        */
       return typed(name, {
    -    'Array, function': _map,
    +    'Array, function': _mapArray,
     
         'Matrix, function': function (x, callback) {
           return x.map(callback)
    -    }
    +    },
    +
    +    'Array|Matrix, Array|Matrix, ...Array|Matrix|function': (A, B, rest) =>
    +      _mapMultiple([A, B, ...rest.slice(0, rest.length - 1)], rest[rest.length - 1])
       })
    +
    +  /**
    + * Maps over multiple arrays or matrices.
    + *
    + * @param {Array<Array|Matrix>} Arrays - An array of arrays or matrices to map over.
    + * @param {function} multiCallback - The callback function to apply to each element.
    + * @throws {Error} If the last argument is not a callback function.
    + * @returns {Array|Matrix} A new array or matrix with each element being the result of the callback function.
    + *
    + * @example
    + * _mapMultiple([[1, 2, 3], [4, 5, 6]], (a, b) => a + b); // Returns [5, 7, 9]
    + */
    +  function _mapMultiple (Arrays, multiCallback) {
    +    if (typeof multiCallback !== 'function') {
    +      throw new Error('Last argument must be a callback function')
    +    }
    +
    +    const firstArrayIsMatrix = Arrays[0].isMatrix
    +
    +    const newSize = broadcastSizes(...Arrays.map(M => M.isMatrix ? M.size() : arraySize(M)))
    +
    +    const _get = firstArrayIsMatrix
    +      ? (matrix, idx) => matrix.get(idx)
    +      : get
    +
    +    const broadcastedArrays = firstArrayIsMatrix
    +      ? Arrays.map(M => M.isMatrix
    +        ? M.create(broadcastTo(M.toArray(), newSize), M.datatype())
    +        : Arrays[0].create(broadcastTo(M.valueOf(), newSize)))
    +      : Arrays.map(M => M.isMatrix
    +        ? broadcastTo(M.toArray(), newSize)
    +        : broadcastTo(M, newSize))
    +
    +    let callback
    +
    +    if (typed.isTypedFunction(multiCallback)) {
    +      const firstIndex = newSize.map(() => 0)
    +      const firstValues = broadcastedArrays.map(array => _get(array, firstIndex))
    +      const callbackCase = _getTypedCallbackCase(multiCallback, firstValues, firstIndex, broadcastedArrays)
    +      callback = _getLimitedCallback(callbackCase)
    +    } else {
    +      const numberOfArrays = Arrays.length
    +      const callbackCase = _getCallbackCase(multiCallback, numberOfArrays)
    +      callback = _getLimitedCallback(callbackCase)
    +    }
    +
    +    const broadcastedArraysCallback = (x, idx) =>
    +      callback(
    +        [x, ...broadcastedArrays.slice(1).map(Array => _get(Array, idx))],
    +        idx)
    +
    +    if (firstArrayIsMatrix) {
    +      return broadcastedArrays[0].map(broadcastedArraysCallback)
    +    } else {
    +      return _mapArray(broadcastedArrays[0], broadcastedArraysCallback)
    +    }
    +
    +    function _getLimitedCallback (callbackCase) {
    +      switch (callbackCase) {
    +        case 0:
    +          return x => multiCallback(...x)
    +        case 1:
    +          return (x, idx) => multiCallback(...x, idx)
    +        case 2:
    +          return (x, idx) => multiCallback(...x, idx, ...broadcastedArrays)
    +      }
    +    }
    +
    +    function _getCallbackCase (callback, numberOfArrays) {
    +      if (callback.length > numberOfArrays + 1) { return 2 }
    +      if (callback.length === numberOfArrays + 1) { return 1 }
    +      return 0
    +    }
    +
    +    function _getTypedCallbackCase (callback, values, idx, arrays) {
    +      if (typed.resolve(callback, [...values, idx, ...arrays]) !== null) { return 2 }
    +      if (typed.resolve(callback, [...values, idx]) !== null) { return 1 }
    +      if (typed.resolve(callback, values) !== null) { return 0 }
    +      // this should never happen
    +      return 0
    +    }
    +  }
     })
     
     /**
    @@ -57,18 +152,27 @@ export const createMap = /* #__PURE__ */ factory(name, dependencies, ({ typed })
      * @return {Array}
      * @private
      */
    -function _map (array, callback) {
    -  const recurse = function (value, index) {
    -    if (Array.isArray(value)) {
    -      return value.map(function (child, i) {
    +function _mapArray (array, callback) {
    +  return _recurse(array, [], array, callback)
    +}
    +
    +/**
    + * 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.
    + */
    +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))
    -      })
    -    } else {
    +      return _recurse(child, index.concat(i), array, callback)
    +    })
    +  } else {
         // invoke the callback function with the right number of arguments
    -      return applyCallback(callback, value, index, array, 'map')
    -    }
    +    return applyCallback(callback, value, index, array, 'map')
       }
    -
    -  return recurse(array, [])
     }
    
  • src/type/matrix/DenseMatrix.js+2 15 modified
    @@ -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 } 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'
    @@ -164,20 +164,7 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies
        * @return {*} value
        */
       DenseMatrix.prototype.get = function (index) {
    -    if (!isArray(index)) { throw new TypeError('Array expected') }
    -    if (index.length !== this._size.length) { throw new DimensionError(index.length, this._size.length) }
    -
    -    // check index
    -    for (let x = 0; x < index.length; x++) { validateIndex(index[x], this._size[x]) }
    -
    -    let data = this._data
    -    for (let i = 0, ii = index.length; i < ii; i++) {
    -      const indexI = index[i]
    -      validateIndex(indexI, data.length)
    -      data = data[indexI]
    -    }
    -
    -    return data
    +    return get(this._data, index)
       }
     
       /**
    
  • src/utils/array.js+20 0 modified
    @@ -805,6 +805,26 @@ export function stretch (arrayToStretch, sizeToStretch, dimToStretch) {
       return concat(...Array(sizeToStretch).fill(arrayToStretch), dimToStretch)
     }
     
    +/**
    +* Retrieves a single element from an array given an index.
    +*
    +* @param {Array} array - The array from which to retrieve the value.
    +* @param {Array<number>} idx - An array of indices specifying the position of the desired element in each dimension.
    +* @returns {*} - The value at the specified position in the array.
    +*
    +* @example
    +* const arr = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]];
    +* const index = [1, 0, 1];
    +* console.log(getValue(arr, index)); // 6
    +*/
    +export function get (array, index) {
    +  if (!Array.isArray(array)) { throw new Error('Array expected') }
    +  const size = arraySize(array)
    +  if (index.length !== size.length) { throw new DimensionError(index.length, size.length) }
    +  for (let x = 0; x < index.length; x++) { validateIndex(index[x], size[x]) }
    +  return index.reduce((acc, curr) => acc[curr], array)
    +}
    +
     /**
      * Deep clones a multidimensional array
      * @param {Array} array
    
  • test/unit-tests/function/matrix/map.test.js+186 5 modified
    @@ -16,6 +16,92 @@ describe('map', function () {
         assert.ok(Array.isArray(arr2))
       })
     
    +  it('should map two arrays', function () {
    +    const arrA = [[1, 2, 3], [4, 5, 6]]
    +    const arrB = [[10, 20, 30], [40, 50, 60]]
    +    const callback = function (valueA, valueB) { return valueA * 2 + valueB }
    +    const arr2 = math.map(arrA, arrB, callback)
    +    const expected = [[12, 24, 36], [48, 60, 72]]
    +    assert.deepStrictEqual(arr2, expected)
    +    assert.ok(Array.isArray(arr2))
    +    const arr3 = math.map(arrA, arrB, math.typed({ 'number, number': callback }))
    +    assert.deepStrictEqual(arr3, expected)
    +    assert.ok(Array.isArray(arr3))
    +  })
    +
    +  it('should map three arrays', function () {
    +    const arrA = [[1, 2, 3], [4, 5, 6]]
    +    const arrB = [[10, 20, 30], [40, 50, 60]]
    +    const arrC = [[100, 200, 300], [400, 500, 600]]
    +    const expected = [[112, 224, 336], [448, 560, 672]]
    +    const callback = function (valueA, valueB, valueC) { return valueA * 2 + valueB + valueC }
    +    const arr2 = math.map(arrA, arrB, arrC, callback)
    +    assert.deepStrictEqual(arr2, expected)
    +    assert.ok(Array.isArray(arr2))
    +    const arr3 = math.map(arrA, arrB, arrC, math.typed({ 'number, number, number': callback }))
    +    assert.deepStrictEqual(arr3, expected)
    +    assert.ok(Array.isArray(arr3))
    +  })
    +
    +  it('should map three arrays with broadcasting', function () {
    +    const arrA = [1, 2, 3]
    +    const arrB = [[10], [20], [30]]
    +    const arrC = [100, 200, 300]
    +    const arr2 = math.map(arrA, arrB, arrC, function (valueA, valueB, valueC) { return valueA * 2 + valueB + valueC / 2 })
    +    assert.deepStrictEqual(arr2, [[62, 114, 166], [72, 124, 176], [82, 134, 186]])
    +    assert.ok(Array.isArray(arr2))
    +  })
    +
    +  it('should map two matrices', function () {
    +    const matA = math.matrix([[1, 2, 3], [4, 5, 6]])
    +    const matB = math.matrix([[10, 20, 30], [40, 50, 60]])
    +    const mat2 = math.map(matA, matB, function (valueA, valueB) { return valueA * 2 + valueB })
    +    assert.deepStrictEqual(mat2, math.matrix([[12, 24, 36], [48, 60, 72]]))
    +    assert.ok(mat2 instanceof math.Matrix)
    +  })
    +
    +  it('should map three matrices', function () {
    +    const matA = math.matrix([[1, 2, 3], [4, 5, 6]])
    +    const matB = math.matrix([[10, 20, 30], [40, 50, 60]])
    +    const matC = math.matrix([[100, 200, 300], [400, 500, 600]])
    +    const expected = math.matrix([[112, 224, 336], [448, 560, 672]])
    +    const callback = function (valueA, valueB, valueC) { return valueA * 2 + valueB + valueC }
    +    const mat2 = math.map(matA, matB, matC, callback)
    +    assert.deepStrictEqual(mat2, expected)
    +    assert.ok(mat2 instanceof math.Matrix)
    +    const mat3 = math.map(matA, matB, matC, math.typed({ 'number, number, number': callback }))
    +    assert.deepStrictEqual(mat3, expected)
    +    assert.ok(mat3 instanceof math.Matrix)
    +  })
    +
    +  it('should map three matrices with broadcasting', function () {
    +    const matA = math.matrix([1, 2, 3])
    +    const matB = math.matrix([[10], [20], [30]])
    +    const matC = math.matrix([100, 200, 300])
    +    const expected = [[62, 114, 166], [72, 124, 176], [82, 134, 186]]
    +    const callback = function (valueA, valueB, valueC) { return valueA * 2 + valueB + valueC / 2 }
    +    const mat2 = math.map(matA, matB, matC, callback)
    +    assert.deepStrictEqual(mat2, math.matrix(expected))
    +    assert.ok(mat2 instanceof math.Matrix)
    +    const mat3 = math.map(matA, matB, matC, math.typed({ 'number, number, number': callback }))
    +    assert.deepStrictEqual(mat3, math.matrix(expected))
    +    assert.ok(mat3 instanceof math.Matrix)
    +  })
    +
    +  it('should map three matrices or arrays with broadcasting', function () {
    +    const matA = math.matrix([1, 2, 3])
    +    const matB = [[10], [20], [30]]
    +    const matC = math.matrix([100, 200, 300])
    +    const expected = math.matrix([[62, 114, 166], [72, 124, 176], [82, 134, 186]])
    +    const callback = function (valueA, valueB, valueC) { return valueA * 2 + valueB + valueC / 2 }
    +    const mat2 = math.map(matA, matB, matC, callback)
    +    assert.deepStrictEqual(mat2, expected)
    +    assert.ok(mat2 instanceof math.Matrix)
    +    const mat3 = math.map(matA, matB, matC, math.typed({ 'number, number, number': callback }))
    +    assert.deepStrictEqual(mat3, expected)
    +    assert.ok(mat3 instanceof math.Matrix)
    +  })
    +
       it('should invoke callback with parameters value, index, obj', function () {
         const arr = [[1, 2, 3], [4, 5, 6]]
     
    @@ -36,7 +122,7 @@ describe('map', function () {
         ])
       })
     
    -  it('should invoke a typed function with correct number of arguments (1)', function () {
    +  it('should invoke a typed function with the correct number of arguments (1)', function () {
         const output = math.map([1, 2, 3], math.typed('callback', {
           number: function (value) {
             return value + 2
    @@ -45,22 +131,69 @@ describe('map', function () {
         assert.deepStrictEqual(output, [3, 4, 5])
       })
     
    +  it('should invoke a typed function with the correct number of arguments (2) for two arrays', function () {
    +    const output = math.map([1, 2, 3], [4, 5, 6], math.typed('callback', {
    +      'number, number': function (a, b) {
    +        return a + b
    +      }
    +    }))
    +    assert.deepStrictEqual(output, [5, 7, 9])
    +  })
    +
    +  it('should invoke a typed function with correct number of arguments (2) for two matrices', function () {
    +    const output = math.map(math.matrix([1, 2, 3]), math.matrix([4, 5, 6]), math.typed('callback', {
    +      'number, number': function (a, b) {
    +        return a + b
    +      }
    +    }))
    +    assert.deepStrictEqual(output, math.matrix([5, 7, 9]))
    +  })
    +
    +  it('should invoke a typed function with correct number of arguments for two matrices and an index', function () {
    +    const callback = function (a, b, idx) {
    +      return a + b + idx[0]
    +    }
    +    const output = math.map(math.matrix([1, 2, 3]), math.matrix([4, 5, 6]), math.typed('callback', {
    +      'number, number, Array': callback
    +    }))
    +    const expected = math.matrix([5, 8, 11])
    +    assert.deepStrictEqual(output, expected)
    +  })
    +
    +  it('should invoke a function with correct number of arguments for two matrices and an index', function () {
    +    const callback = function (a, b, idx) {
    +      return a + b + idx[0]
    +    }
    +    const output = math.map(math.matrix([1, 2, 3]), math.matrix([4, 5, 6]), callback)
    +    const expected = math.matrix([5, 8, 11])
    +    assert.deepStrictEqual(output, expected)
    +  })
    +
    +  it('should invoke a function with correct number of arguments for two matrices, index and original matrices', function () {
    +    const callback = function (a, b, idx, A, B) {
    +      return a + b + A.get(idx) + B.get(idx) + idx[0]
    +    }
    +    const output = math.map(math.matrix([1, 2, 3]), math.matrix([4, 5, 6]), callback)
    +    const expected = math.matrix([10, 15, 20])
    +    assert.deepStrictEqual(output, expected)
    +  })
    +
       it('should invoke a typed function with correct number of arguments (2)', function () {
         const output = math.map([1, 2, 3], math.typed('callback', {
           'number, Array': function (value, index) {
    -        return value + 2
    +        return value + 2 * index[0]
           }
         }))
    -    assert.deepStrictEqual(output, [3, 4, 5])
    +    assert.deepStrictEqual(output, [1, 4, 7])
       })
     
       it('should invoke a typed function with correct number of arguments (3)', function () {
         const output = math.map([1, 2, 3], math.typed('callback', {
           'number, Array, Array': function (value, index, array) {
    -        return value + 2
    +        return value + index[0] + array[1]
           }
         }))
    -    assert.deepStrictEqual(output, [3, 4, 5])
    +    assert.deepStrictEqual(output, [3, 5, 7])
       })
     
       it('should invoke a typed function with correct number of arguments (4)', function () {
    @@ -96,6 +229,14 @@ describe('map', function () {
           /TypeError: Function map cannot apply callback arguments sqrt\(value: function, index: Array, array: Array\) at index \[0]/)
       })
     
    +  it('should throw an error if the last argument of a mullti callback function is not a function', function () {
    +    assert.throws(() => math.map([1], [2], 'not a function'),
    +      /TypeError: Unexpected type of argument in function map/)
    +
    +    assert.throws(() => math.map([1], [2], ['not a function']),
    +      /Last argument must be a callback function/)
    +  })
    +
       it('should operate from the parser', function () {
         assert.deepStrictEqual(
           math.evaluate('map([1,2,3], square)'),
    @@ -105,6 +246,46 @@ describe('map', function () {
           math.matrix(['1', '2', '3']))
       })
     
    +  it('should operate from the parser with multiple inputs', function () {
    +    assert.deepStrictEqual(
    +      math.evaluate('map([1, 2], [3, 4], f)', { f: (a, b) => a + b }),
    +      math.matrix([4, 6])
    +    )
    +    assert.deepStrictEqual(
    +      math.evaluate('map([1, 2], [3, 4], _(a, b) = a + b)'),
    +      math.matrix([4, 6])
    +    )
    +    assert.deepStrictEqual(
    +      math.evaluate('map([[1, 2, 3], [4, 5, 6]], [[10, 20, 30], [40, 50, 60]], f(a, b) = a * 2 + b)'),
    +      math.matrix([[12, 24, 36], [48, 60, 72]])
    +    )
    +  })
    +
    +  it('should operate from the parser with three arrays with broadcasting', function () {
    +    const arr2 = math.evaluate('map([1, 2, 3], [[10], [20], [30]], [100, 200, 300], _(A, B, C) = A * 2 + B + C / 2)')
    +    assert.deepStrictEqual(arr2, math.matrix([[62, 114, 166], [72, 124, 176], [82, 134, 186]]))
    +  })
    +
    +  it('should operate from the parser with multiple inputs and one based indices', function () {
    +    const arr2 = math.evaluate('map([1,2],[3,4], f(a,b,idx)=a+b+idx[1])')
    +    const expected = math.matrix([5, 8])
    +    assert.deepStrictEqual(arr2, expected)
    +  })
    +
    +  it('should operate from the parser with multiple inputs that need broadcasting and one based indices', function () {
    +    const arr2 = math.evaluate('map([1],[3,4], f(a,b,idx)=a+b+idx[1])')
    +    const expected = math.matrix([5, 7])
    +    assert.deepStrictEqual(arr2, expected)
    +  })
    +
    +  it('should operate from the parser with multiple inputs that need broadcasting and one based indices and the broadcasted arrays', function () {
    +    // this is a convoluted way of calculating f(a,b,idx) = 2a+2b+index
    +    // 2(1) + 2([3,4]) + [1, 2] # yields [9, 12]
    +    const arr2 = math.evaluate('map([1],[3,4], f(a,b,idx,A,B)= a + A[idx] + b + B[idx] + idx[1])')
    +    const expected = math.matrix([9, 12])
    +    assert.deepStrictEqual(arr2, expected)
    +  })
    +
       it('should LaTeX map', function () {
         const expression = math.parse('map([1,2,3],callback)')
         assert.strictEqual(expression.toTex(), '\\mathrm{map}\\left(\\begin{bmatrix}1\\\\2\\\\3\\end{bmatrix}, callback\\right)')
    
  • test/unit-tests/utils/array.test.js+30 1 modified
    @@ -17,7 +17,8 @@ import {
       concat,
       checkBroadcastingRules,
       stretch,
    -  broadcastArrays
    +  broadcastArrays,
    +  get
     } from '../../../src/utils/array.js'
     
     describe('util.array', function () {
    @@ -651,6 +652,34 @@ describe('util.array', function () {
         })
       })
     
    +  describe('get', function () {
    +    const m = [[0, 1], [2, 3]]
    +
    +    it('should get a value from the array', function () {
    +      assert.strictEqual(get(m, [1, 0]), 2)
    +      assert.strictEqual(get(m, [0, 1]), 1)
    +    })
    +
    +    it('should throw an error when getting a value out of range', function () {
    +      assert.throws(function () { get(m, [3, 0]) })
    +      assert.throws(function () { get(m, [1, 5]) })
    +      assert.throws(function () { get(m, [1]) })
    +      assert.throws(function () { get(m, []) })
    +    })
    +
    +    it('should throw an error in case of dimension mismatch', function () {
    +      assert.throws(function () { get(m, [0, 2, 0, 2, 0, 2]) }, /Dimension mismatch/)
    +    })
    +
    +    it('should throw an error when getting a value given a invalid index', function () {
    +      assert.throws(function () { get(m, [1.2, 2]) })
    +      assert.throws(function () { get(m, [1, -2]) })
    +      assert.throws(function () { get(m, 1, 1) })
    +      assert.throws(function () { get(m, null) })
    +      assert.throws(function () { get(m, [[1, 1]]) })
    +    })
    +  })
    +
       describe('checkBroadcastingRules', function () {
         it('should not throw an error if the broadcasting rules are ok', function () {
           assert.doesNotThrow(function () { checkBroadcastingRules([1, 2], [1, 2]) })
    
  • types/index.d.ts+23 1 modified
    @@ -1967,7 +1967,29 @@ export interface MathJsInstance extends MathJsFactory {
       map<T extends MathCollection>(
         x: T,
         // eslint-disable-next-line @typescript-eslint/no-explicit-any
    -    callback: (value: any, index: any, matrix: T) => MathType | string
    +    callback: (value: any, index: number[], matrix: T) => MathType | string
    +  ): T
    +
    +  /**
    +   * Iterate over all elements of multiple matrices/arrays, and executes the given
    +   * callback function.
    +   * @param x The first matrix to iterate on.
    +   * @param args The rest of the matrices and at the end the callback function is invoked with multiple
    +   * parameters: the values of the elements, the indices of the elements, and
    +   * the matrices/arrays being traversed.
    +   * @returns Transformed map of matrices
    +   */
    +  map<T extends MathCollection>(
    +    x: T,
    +    ...args: Array<
    +      | T
    +      | ((
    +          // eslint-disable-next-line @typescript-eslint/no-explicit-any
    +          value: any,
    +          // eslint-disable-next-line @typescript-eslint/no-explicit-any
    +          ...args: Array<any | number[] | T>
    +        ) => MathType | string)
    +    >
       ): T
     
       /**
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

8

News mentions

0

No linked articles in our index yet.