Skip to content

Commit

Permalink
Merge branch 'Undo' of https://github.com/Veeloxfire/mathjs into Veel…
Browse files Browse the repository at this point in the history
…oxfire-Undo
  • Loading branch information
josdejong committed Jul 19, 2020
2 parents 76a0ec7 + 1fd3c8b commit 84f2746
Show file tree
Hide file tree
Showing 7 changed files with 468 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/expression/embeddedDocs/embeddedDocs.js
Expand Up @@ -84,6 +84,7 @@ import { subsetDocs } from './function/matrix/subset'
import { squeezeDocs } from './function/matrix/squeeze'
import { sortDocs } from './function/matrix/sort'
import { sizeDocs } from './function/matrix/size'
import { diffDocs } from './function/matrix/diff'
import { reshapeDocs } from './function/matrix/reshape'
import { resizeDocs } from './function/matrix/resize'
import { rangeDocs } from './function/matrix/range'
Expand Down Expand Up @@ -402,6 +403,7 @@ export const embeddedDocs = {
ctranspose: ctransposeDocs,
det: detDocs,
diag: diagDocs,
diff: diffDocs,
dot: dotDocs,
getMatrixDataType: getMatrixDataTypeDocs,
identity: identityDocs,
Expand Down
29 changes: 29 additions & 0 deletions src/expression/embeddedDocs/function/matrix/diff.js
@@ -0,0 +1,29 @@
export const diffDocs = {
name: 'diff',
category: 'Matrix',
syntax: [
'diff(arr)',
'diff(arr, dim)'
],
description: [
'Create a new matrix or array with the difference of the passed matrix or array.',
'Dim parameter is optional and used to indicant the dimension of the array/matrix to apply the difference',
'If no dimension parameter is passed it is assumed as dimension 0',
'Dimension is zero-based in javascript and one-based in the parser',
'Arrays must be \'rectangular\' meaning arrays like [1, 2]',
'If something is passed as a matrix it will be returned as a matrix but other than that all matrices are converted to arrays'
],
examples: [
'diff([1, 2, 4, 7, 0])',
'diff([1, 2, 4, 7, 0], 0)',
'diff(matrix([1, 2, 4, 7, 0]))',
'diff([[1, 2], [3, 4]])',
'diff([[1, 2], [3, 4]], 0)',
'diff([[1, 2], [3, 4]], 1)',
'diff([[1, 2], [3, 4]], bignumber(1))',
'diff(matrix([[1, 2], [3, 4]]), 1)',
'diff([[1, 2], matrix([3, 4])], 1)'

],
seealso: ['subtract', 'partitionSelect']
}
37 changes: 37 additions & 0 deletions src/expression/transform/diff.transform.js
@@ -0,0 +1,37 @@
import { isBigNumber, isCollection, isNumber } from '../../utils/is'
import { factory } from '../../utils/factory'
import { errorTransform } from './utils/errorTransform'
import { createDiff } from '../../function/matrix/diff'

const name = 'diff'
const dependencies = ['typed', 'matrix', 'subtract', 'number', 'bignumber']

export const createDiffTransform = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, subtract, number, bignumber }) => {
const diff = createDiff({ typed, matrix, subtract, number, bignumber })

/**
* Attach a transform function to math.diff
* Adds a property transform containing the transform function.
*
* This transform creates a range which includes the end value
*/
return typed(name, {
'...any': function (args) {
// change last argument dim from one-based to zero-based
if (args.length === 2 && isCollection(args[0])) {
const dim = args[1]
if (isNumber(dim)) {
args[1] = dim - 1
} else if (isBigNumber(dim)) {
args[1] = dim.minus(1)
}
}

try {
return diff.apply(null, args)
} catch (err) {
throw errorTransform(err)
}
}
})
}, { isTransformFunction: true })
2 changes: 2 additions & 0 deletions src/factoriesAny.js
Expand Up @@ -73,6 +73,7 @@ export { createGetMatrixDataType } from './function/matrix/getMatrixDataType'
export { createIdentity } from './function/matrix/identity'
export { createKron } from './function/matrix/kron'
export { createMap } from './function/matrix/map'
export { createDiff } from './function/matrix/diff'
export { createOnes } from './function/matrix/ones'
export { createRange } from './function/matrix/range'
export { createReshape } from './function/matrix/reshape'
Expand Down Expand Up @@ -319,6 +320,7 @@ export { createRangeTransform } from './expression/transform/range.transform'
export { createRowTransform } from './expression/transform/row.transform'
export { createSubsetTransform } from './expression/transform/subset.transform'
export { createConcatTransform } from './expression/transform/concat.transform'
export { createDiffTransform } from './expression/transform/diff.transform'
export { createStdTransform } from './expression/transform/std.transform'
export { createSumTransform } from './expression/transform/sum.transform'
export { createVarianceTransform } from './expression/transform/variance.transform'
170 changes: 170 additions & 0 deletions src/function/matrix/diff.js
@@ -0,0 +1,170 @@
import { factory } from '../../utils/factory'
import { isInteger } from '../../utils/number'
import { isMatrix } from '../../utils/is'

const name = 'diff'
const dependencies = ['typed', 'matrix', 'subtract', 'number', 'bignumber']

export const createDiff = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, subtract, number, bignumber }) => {
/**
* Create a new matrix or array of the difference between elements of the given array
* The optional dim parameter lets you specify the dimension to evaluate the difference of
* If no dimension parameter is passed it is assumed as dimension 0
*
* Dimension is zero-based in javascript and one-based in the parser and can be a number or bignumber
* Arrays must be 'rectangular' meaning arrays like [1, 2]
* If something is passed as a matrix it will be returned as a matrix but other than that all matrices are converted to arrays
*
* Syntax:
*
* math.diff(arr)
* math.diff(arr, dim)
*
* Examples:
*
* const arr = [1, 2, 4, 7, 0]
* math.diff(arr) // returns [1, 2, 3, -7] (no dimension passed so 0 is assumed)
* math.diff(math.matrix(arr)) // returns math.matrix([1, 2, 3, -7])
*
* const arr = [[1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [9, 8, 7, 6, 4]]
* math.diff(arr) // returns [[0, 0, 0, 0, 0], [8, 6, 4, 2, -1]]
* math.diff(arr, 0) // returns [[0, 0, 0, 0, 0], [8, 6, 4, 2, -1]]
* math.diff(arr, 1) // returns [[1, 1, 1, 1], [1, 1, 1, 1], [-1, -1, -1, -2]]
* math.diff(arr, math.bignumber(1)) // returns [[1, 1, 1, 1], [1, 1, 1, 1], [-1, -1, -1, -2]]
*
* math.diff(arr, 2) // throws RangeError as arr is 2 dimensional not 3
* math.diff(arr, -1) // throws RangeError as negative dimensions are not allowed
*
* // These will all produce the same result
* math.diff([[1, 2], [3, 4]])
* math.diff([math.matrix([1, 2]), math.matrix([3, 4])])
* math.diff([[1, 2], math.matrix([3, 4])])
* math.diff([math.matrix([1, 2]), [3, 4]])
* // They do not produce the same result as math.diff(math.matrix([[1, 2], [3, 4]])) as this returns a matrix
*
* See Also:
*
* Subtract
* PartitionSelect
*
* @param {Array | Matrix} arr An array or matrix
* @param {number} dim Dimension
* @return {Array | Matrix} Difference between array elements in given dimension
*/
return typed(name, {
'Array | Matrix': function (arr) { // No dimension specified => assume dimension 0
if (isMatrix(arr)) {
return matrix(_diff(arr.toArray()))
} else {
return _diff(arr)
}
},
'Array | Matrix, number': function (arr, dim) {
if (!isInteger(dim)) throw RangeError('Dimension must be a whole number')
if (isMatrix(arr)) {
return matrix(_recursive(arr.toArray(), dim))
} else {
return _recursive(arr, dim)
}
},
'Array | Matrix, BigNumber': function (arr, dim) {
const maxInt = bignumber(Number.MAX_SAFE_INTEGER)
const minInt = bignumber(Number.MIN_SAFE_INTEGER)
if (dim > maxInt || dim < minInt) throw RangeError('The array does not have more than 2^53 dimensions')
if (!dim.isInt()) throw RangeError('Dimension must be a whole number')
if (isMatrix(arr)) {
return matrix(_recursive(arr.toArray(), number(dim)))
} else {
return _recursive(arr, number(dim))
}
}
})

/**
* Recursively find the correct dimension in the array/matrix
* Then Apply _diff to that dimension
*
* @param {Array} arr The array
* @param {number} dim Dimension
* @return {Array} resulting array
*/
function _recursive (arr, dim) {
if (isMatrix(arr)) {
arr = arr.toArray() // Makes sure arrays like [ matrix([0, 1]), matrix([1, 0]) ] are processed properly
}
if (!Array.isArray(arr)) {
throw RangeError('Array/Matrix does not have that many dimensions')
}
if (dim > 0) {
const result = []
arr.forEach(element => {
result.push(_recursive(element, dim - 1))
})
return result
} else if (dim === 0) {
return _diff(arr)
} else {
throw RangeError('Cannot have negative dimension')
}
}

/**
* Difference between elements in the array
*
* @param {Array} arr An array
* @return {Array} resulting array
*/
function _diff (arr) {
const result = []
const size = arr.length
if (size < 2) {
return arr
}
for (let i = 1; i < size; i++) {
result.push(_ElementDiff(arr[i - 1], arr[i]))
}
return result
}

/**
* Difference between 2 objects
*
* @param {Object} obj1 First object
* @param {Object} obj2 Second object
* @return {Array} resulting array
*/
function _ElementDiff (obj1, obj2) {
// Convert matrices to arrays
if (isMatrix(obj1)) obj1 = obj1.toArray()
if (isMatrix(obj2)) obj2 = obj2.toArray()

const obj1IsArray = Array.isArray(obj1)
const obj2IsArray = Array.isArray(obj2)
if (obj1IsArray && obj2IsArray) {
return _ArrayDiff(obj1, obj2)
}
if (!obj1IsArray && !obj2IsArray) {
return subtract(obj2, obj1) // Difference is (second - first) NOT (first - second)
}
throw TypeError('Cannot calculate difference between 1 array and 1 non-array')
}

/**
* Difference of elements in 2 arrays
*
* @param {Array} arr1 Array 1
* @param {Array} arr2 Array 2
* @return {Array} resulting array
*/
function _ArrayDiff (arr1, arr2) {
if (arr1.length !== arr2.length) {
throw RangeError('Not all sub-arrays have the same length')
}
const result = []
const size = arr1.length
for (let i = 0; i < size; i++) {
result.push(_ElementDiff(arr1[i], arr2[i]))
}
return result
}
})
36 changes: 36 additions & 0 deletions test/unit-tests/expression/transform/diff.transform.test.js
@@ -0,0 +1,36 @@
import assert from 'assert'
import math from '../../../../src/bundleAny'

const diff = math.expression.transform.diff

describe('diff.transform', function () {
it('Should use one-based indexing for dimensions with arrays', function () {
// With Dim = 1
assert.deepStrictEqual(diff([1, 2, 4, 7, 0], 1), [1, 2, 3, -7])
assert.deepStrictEqual(diff([1, 2, 4, 7, 0], math.bignumber(1)), [1, 2, 3, -7])

// Without Dim = 1
assert.deepStrictEqual(diff([1, 2, 4, 7, 0]), [1, 2, 3, -7])
})

it('Should use one-based indexing for dimensions with matrices', function () {
// With Dim = 1
assert.deepStrictEqual(diff(math.matrix([1, 2, 4, 7, 0]), 1), math.matrix([1, 2, 3, -7]))
assert.deepStrictEqual(diff(math.matrix([1, 2, 4, 7, 0]), math.bignumber(1)), math.matrix([1, 2, 3, -7]))

// Without Dim = 1
assert.deepStrictEqual(diff(math.matrix([1, 2, 4, 7, 0])), math.matrix([1, 2, 3, -7]))
})

it('should throw an error if the dimension is below the range for one based indices', function () {
assert.throws(function () { diff(math.matrix([1, 2, 4, 7, 0]), 0) }, Error)
})

it('should throw an error if the dimension is above the range for one based indices', function () {
assert.throws(function () { diff(math.matrix([1, 2, 4, 7, 0]), math.bignumber(0)) }, Error)
})

it('should work with the parser', function () {
assert.deepStrictEqual(math.evaluate('diff([1, 2, 4, 7, 0], 1)'), math.matrix([1, 2, 3, -7]))
})
})

0 comments on commit 84f2746

Please sign in to comment.