Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Diff function #1812

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 @@ -403,6 +404,7 @@ export const embeddedDocs = {
ctranspose: ctransposeDocs,
det: detDocs,
diag: diagDocs,
diff: diffDocs,
dot: dotDocs,
getMatrixDataType: getMatrixDataTypeDocs,
identity: identityDocs,
Expand Down
18 changes: 18 additions & 0 deletions src/expression/embeddedDocs/function/matrix/diff.js
@@ -0,0 +1,18 @@
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 diffrence (if not used it is assumed as 0)'
],
examples: [
'diff([1, 2, 4, 7, 0])',
'diff([1, 2, 4, 7, 0], 0)',
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this example should be a 2d array so you can actually see what the dimension does, like

'diff([[1, 2], [3, 6]], 0)',
'diff([[1, 2], [3, 6]], 1)',

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, I think I did this because it was just a copy and paste but yes should definitely have more comprehensive examples

'diff(matrix([1, 2, 4, 7, 0]))'
],
seealso: ['subtract']
}
1 change: 1 addition & 0 deletions src/factoriesAny.js
Expand Up @@ -74,6 +74,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
133 changes: 133 additions & 0 deletions src/function/matrix/diff.js
@@ -0,0 +1,133 @@
import { factory } from '../../utils/factory'
import { isMatrix } from '../../utils/is'

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

export const createDiff = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, subtract }) => {
/**
* 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
*
* 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 [1, 2, 3, -7] as matrix
*
* const arr = [1]
* math.diff(arr) // returns [1]
*
* 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) // returns [[1, 1, 1, 1], [1, 1, 1, 1], [-1, -1, -1, -2]]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think here the dimension should be specified, like math.diff(arr, 1)?

*
* See Also:
*
* Subtract
*
* @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 1
if (isMatrix(arr)) {
return matrix(_diff(arr.toArray()))
} else {
return _diff(arr)
}
},
'Array | Matrix, number': function (arr, dim) {
if (isMatrix(arr)) {
return matrix(_recursive(arr.toArray(), dim))
} else {
return _recursive(arr, 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 (!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
}
return _diff(arr)
}

/**
* 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) {
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 Error('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 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
}
})
89 changes: 89 additions & 0 deletions test/unit-tests/function/matrix/diff.test.js
@@ -0,0 +1,89 @@
import assert from 'assert'
import math from '../../../../src/bundleAny'

const matrix = math.matrix

const smallTestArray = [[1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [9, 8, 7, 6, 4]]

const smallTestArrayDimension0 = [[0, 0, 0, 0, 0], [8, 6, 4, 2, -1]]
const smallTestArrayDimension1 = [[1, 1, 1, 1], [1, 1, 1, 1], [-1, -1, -1, -2]]

// largeTestArrayDimension0-3 were generated from largeTestArray using numpy.diff() for consistency. Please dont change them because they are a pain to get linted correctly
const largeTestArray = [[[[1, 2, 3], [2, 3, 4], [3, 4, 5]], [[3, 2, 1], [2, 3, 4], [5, 4, 3]], [[5, 2, 1], [5, 2, 1], [5, 2, 1]]], [[[8, 12, 345], [23, 35, 23], [123, 234, 12]], [[1, 2, 3], [1, 3, 5], [5, 6, 7]], [[66, 55, 44], [32, 32, 1], [0, 1, 2]]], [[[1, 64, 3], [2, 34, 61], [128, 92, 64]], [[12, 2, 1], [6, 8, 9], [2, 7, 3]], [[52, 22, 21], [55, 52, 51], [111, 12, 11]]]]

const largeTestArrayDimension0 = [[[[7, 10, 342], [21, 32, 19], [120, 230, 7]], [[-2, 0, 2], [-1, 0, 1], [0, 2, 4]], [[61, 53, 43], [27, 30, 0], [-5, -1, 1]]], [[[-7, 52, -342], [-21, -1, 38], [5, -142, 52]], [[11, 0, -2], [5, 5, 4], [-3, 1, -4]], [[-14, -33, -23], [23, 20, 50], [111, 11, 9]]]]
const largeTestArrayDimension1 = [[[[2, 0, -2], [0, 0, 0], [2, 0, -2]], [[2, 0, 0], [3, -1, -3], [0, -2, -2]]], [[[-7, -10, -342], [-22, -32, -18], [-118, -228, -5]], [[65, 53, 41], [31, 29, -4], [-5, -5, -5]]], [[[11, -62, -2], [4, -26, -52], [-126, -85, -61]], [[40, 20, 20], [49, 44, 42], [109, 5, 8]]]]
const largeTestArrayDimension2 = [[[[1, 1, 1], [1, 1, 1]], [[-1, 1, 3], [3, 1, -1]], [[0, 0, 0], [0, 0, 0]]], [[[15, 23, -322], [100, 199, -11]], [[0, 1, 2], [4, 3, 2]], [[-34, -23, -43], [-32, -31, 1]]], [[[1, -30, 58], [126, 58, 3]], [[-6, 6, 8], [-4, -1, -6]], [[3, 30, 30], [56, -40, -40]]]]
const largeTestArrayDimension3 = [[[[1, 1], [1, 1], [1, 1]], [[-1, -1], [1, 1], [-1, -1]], [[-3, -1], [-3, -1], [-3, -1]]], [[[4, 333], [12, -12], [111, -222]], [[1, 1], [2, 2], [1, 1]], [[-11, -11], [0, -31], [1, 1]]], [[[63, -61], [32, 27], [-36, -28]], [[-10, -1], [2, 1], [5, -4]], [[-30, -1], [-3, -1], [-99, -1]]]]

describe('diff', function () {
it('should return original array/matrix for less than 2 elements, with and without specified dimension', function () {
// With Dim = 0 specified
assert.deepStrictEqual(math.diff([], 0), [])
assert.deepStrictEqual(math.diff(matrix([]), 0), matrix([]))
assert.deepStrictEqual(math.diff([2], 0), [2])
assert.deepStrictEqual(math.diff(matrix([2]), 0), matrix([2]))

// Without Dim = 0 specified
assert.deepStrictEqual(math.diff([]), [])
assert.deepStrictEqual(math.diff(matrix([])), matrix([]))
assert.deepStrictEqual(math.diff([2]), [2])
assert.deepStrictEqual(math.diff(matrix([2])), matrix([2]))
})

it('should return difference between elements of a 1-dimensional array, with and without specified dimension', function () {
// With Dim = 0
assert.deepStrictEqual(math.diff([1, 2, 4, 7, 0], 0), [1, 2, 3, -7])

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

it('should return difference between elements of a 1-dimensional matrix, with and without specified dimension', function () {
// With Dim = 0
assert.deepStrictEqual(math.diff(matrix([1, 2, 4, 7, 0]), 0), matrix([1, 2, 3, -7]))

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

it('should return difference between elements of a 2-dimensional array, with and without specified dimension', function () {
// With Dim specified
assert.deepStrictEqual(math.diff(smallTestArray, 0), smallTestArrayDimension0)
assert.deepStrictEqual(math.diff(smallTestArray, 1), smallTestArrayDimension1)

// Without Dim specified
assert.deepStrictEqual(math.diff(smallTestArray), smallTestArrayDimension0)
})

it('should return difference between elements of a 2-dimensional matrix, with and without specified dimension', function () {
// With Dim specified
assert.deepStrictEqual(math.diff(matrix(smallTestArray), 0), matrix(smallTestArrayDimension0))
assert.deepStrictEqual(math.diff(matrix(smallTestArray), 1), matrix(smallTestArrayDimension1))

// Without Dim specified
assert.deepStrictEqual(math.diff(matrix(smallTestArray)), matrix(smallTestArrayDimension0))
})

it('should return difference between elements of a 4-dimensional array, with and without specified dimension', function () {
// With Dim specified
assert.deepStrictEqual(math.diff(largeTestArray, 0), largeTestArrayDimension0)
assert.deepStrictEqual(math.diff(largeTestArray, 1), largeTestArrayDimension1)
assert.deepStrictEqual(math.diff(largeTestArray, 2), largeTestArrayDimension2)
assert.deepStrictEqual(math.diff(largeTestArray, 3), largeTestArrayDimension3)

// Without Dim specified
assert.deepStrictEqual(math.diff(largeTestArray), largeTestArrayDimension0)
})

it('should return difference between elements of a 4-dimensional matrix, with and without specified dimension', function () {
// With Dim specified
assert.deepStrictEqual(math.diff(matrix(largeTestArray), 0), matrix(largeTestArrayDimension0))
assert.deepStrictEqual(math.diff(matrix(largeTestArray), 1), matrix(largeTestArrayDimension1))
assert.deepStrictEqual(math.diff(matrix(largeTestArray), 2), matrix(largeTestArrayDimension2))
assert.deepStrictEqual(math.diff(matrix(largeTestArray), 3), matrix(largeTestArrayDimension3))

// Without Dim specified
assert.deepStrictEqual(math.diff(matrix(largeTestArray)), matrix(largeTestArrayDimension0))
})
})