From 0dfdf3b2a29c8b7a96b843edef19abfbd2f76ab9 Mon Sep 17 00:00:00 2001 From: josdejong Date: Wed, 25 Sep 2013 21:17:28 +0200 Subject: [PATCH] Removed concatenation of nested arrays --- HISTORY.md | 8 +- README.md | 8 +- bower.json | 2 +- docs/index.md | 4 +- docs/matrices.md | 4 +- examples/expressions.js | 4 +- lib/expression/node/MatrixNode.js | 29 ++--- lib/function/expression/parse.js | 148 ++++++++++++------------- package.json | 2 +- test/function/expression/parse.test.js | 49 ++++---- 10 files changed, 128 insertions(+), 130 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index b63fbab5b..ff31fd9ad 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,8 +2,14 @@ https://github.com/josdejong/mathjs -## not yet released, version 0.13.1 +## not yet released, version 0.14.0 +- Removed concatenation of nested arrays in the expression parser. + You can now input nested arrays like in JavaScript. Matrices can be + concatenated using the function `concat`. +- The matrix syntax `[...]` in the expression parser now creates 1 dimensional + matrices by default. `math.eval('[1,2,3,4]')` returns a matrix with size `[4]`, + `math.eval('[1,2;3,4]')` returns a matrix with size `[2,2]`. - Fixed non working operator `mod` (modulus operator). diff --git a/README.md b/README.md index 8ab710556..255ed4349 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Powerful and easy to use. Math.js can be installed using npm or bower, or by [downloading](http://mathjs.org/#install_or_download) the library. The library can be used in both node.js and in the browser. -See the [Getting Start](https://github.com/josdejong/mathjs/blob/master/docs/getting_started.md) for a more detailed tutorial. To install math.js using npm: +See the [Getting Started](https://github.com/josdejong/mathjs/blob/master/docs/getting_started.md) for a more detailed tutorial. To install math.js using npm: npm install mathjs @@ -62,9 +62,9 @@ math.select(3) ## Documentation -[Getting Started](https://github.com/josdejong/mathjs/blob/master/docs/getting_started.md) • -[Examples](https://github.com/josdejong/mathjs/tree/master/examples/) • -[Documentation](https://github.com/josdejong/mathjs/blob/master/docs/index.md) +- [Getting Started](https://github.com/josdejong/mathjs/blob/master/docs/getting_started.md) +- [Examples](https://github.com/josdejong/mathjs/tree/master/examples/) +- [Documentation](https://github.com/josdejong/mathjs/blob/master/docs/index.md) ## Build diff --git a/bower.json b/bower.json index f132ba930..3c1c9f615 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "mathjs", - "version": "0.13.1-SNAPSHOT", + "version": "0.14.0-SNAPSHOT", "main": "./dist/math.js", "ignore": [ "coverage", diff --git a/docs/index.md b/docs/index.md index 9a5beaa6c..ebc58a443 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,6 +1,4 @@ -# Overview - -The documentation of math.js contains the following pages: +# Math.js Documentation - [Getting Started](getting_started.md) - [Constants](constants.md) diff --git a/docs/matrices.md b/docs/matrices.md index b1382e41d..f339163c9 100644 --- a/docs/matrices.md +++ b/docs/matrices.md @@ -41,8 +41,8 @@ parser = math.parser(); parser.eval('a = [1, 2; 3, 4]'); // Matrix, [[1, 2], [3, 4]] parser.eval('b = zeros(2, 2)'); // Matrix, [[0, 0], [0, 0]] -parser.eval('b(1, 1:2) = [5, 6]'); // Matrix, [[5, 6], [0, 0]] -parser.eval('b(2, :) = [7, 8]'); // Matrix, [[5, 6], [7, 8]] +parser.eval('b(1, 1:2) = [[5, 6]]'); // Matrix, [[5, 6], [0, 0]] +parser.eval('b(2, :) = [[7, 8]]'); // Matrix, [[5, 6], [7, 8]] parser.eval('c = a * b'); // Matrix, [[19, 22], [43, 50]] parser.eval('d = c(2, 1)'); // 43 parser.eval('e = c(2, 1:end)'); // Matrix, [[43, 50]] diff --git a/examples/expressions.js b/examples/expressions.js index b693bb5d9..49f34c9e5 100644 --- a/examples/expressions.js +++ b/examples/expressions.js @@ -135,8 +135,8 @@ print(parser.eval('f(2, 3)')); // 8 console.log('\nmanipulate matrices'); print(parser.eval('k = [1, 2; 3, 4]')); // [[1, 2], [3, 4]] print(parser.eval('l = zeros(2, 2)')); // [[0, 0], [0, 0]] -print(parser.eval('l(1, 1:2) = [5, 6]')); // [[5, 6], [0, 0]] -print(parser.eval('l(2, :) = [7, 8]')); // [[5, 6], [7, 8]] +print(parser.eval('l(1, 1:2) = [[5, 6]]')); // [[5, 6], [0, 0]] +print(parser.eval('l(2, :) = [[7, 8]]')); // [[5, 6], [7, 8]] print(parser.eval('m = k * l')); // [[19, 22], [43, 50]] print(parser.eval('n = m(2, 1)')); // 43 print(parser.eval('n = m(:, 1)')); // [[19], [43]] diff --git a/lib/expression/node/MatrixNode.js b/lib/expression/node/MatrixNode.js index cf94b8029..d18d7a647 100644 --- a/lib/expression/node/MatrixNode.js +++ b/lib/expression/node/MatrixNode.js @@ -6,8 +6,8 @@ var Node = require('./Node.js'), /** * @constructor MatrixNode - * Holds an 2-dimensional array with nodes - * @param {Array[]} nodes 2 dimensional array with nodes + * Holds an 1-dimensional array with nodes + * @param {Array} nodes 1 dimensional array with nodes * @extends {Node} */ function MatrixNode(nodes) { @@ -22,26 +22,14 @@ MatrixNode.prototype = new Node(); * @override */ MatrixNode.prototype.eval = function() { - // evaluate all nodes in the 2d array, and merge the results into a matrix + // evaluate all nodes in the array, and merge the results into a matrix var nodes = this.nodes, - results = [], - mergeNeeded = false; + results = []; - for (var r = 0, rows = nodes.length; r < rows; r++) { - var nodes_r = nodes[r]; - var results_r = []; - for (var c = 0, cols = nodes_r.length; c < cols; c++) { - var results_rc = nodes_r[c].eval(); - if (collection.isCollection(results_rc)) { - mergeNeeded = true; - } - results_r[c] = results_rc; - } - results[r] = results_r; - } - - if (mergeNeeded) { - results = merge(results); + for (var i = 0, ii = nodes.length; i < ii; i++) { + var node = nodes[i]; + var result = node.eval(); + results[i] = (result instanceof Matrix) ? result.valueOf() : result; } return new Matrix(results); @@ -77,6 +65,7 @@ MatrixNode.prototype.find = function (filter) { * @param {Array} array Two-dimensional array containing Matrices * @return {Array} merged The merged array (two-dimensional) */ +// TODO: cleanup merge function function merge (array) { var merged = []; var rows = array.length; diff --git a/lib/function/expression/parse.js b/lib/function/expression/parse.js index b47b45ed1..f59a7aa05 100644 --- a/lib/function/expression/parse.js +++ b/lib/function/expression/parse.js @@ -1046,7 +1046,7 @@ module.exports = function (math) { * @private */ function parseMatrix (scope) { - var array, params, r, c, rows, cols; + var array, params, rows, cols; if (token == '[') { // matrix [...] @@ -1057,92 +1057,58 @@ module.exports = function (math) { getToken(); } - // check if this is an empty matrix "[ ]" if (token != ']') { // this is a non-empty matrix - params = []; - r = 0; - c = 0; + var row = parseRow(scope); - params[0] = [parseAssignment(scope)]; + if (token == ';') { + // 2 dimensional array + rows = 1; + params = [row]; - // the columns in the matrix are separated by commas, and the rows by dot-comma's - while (token == ',' || token == ';') { - if (token == ',') { - c++; - } - else { - r++; - c = 0; - params[r] = []; + // the rows of the matrix are separated by dot-comma's + while (token == ';') { + getToken(); + + // skip newlines + while (token == '\n') { + getToken(); + } + + params[rows] = parseRow(scope); + rows++; + + // skip newlines + while (token == '\n') { + getToken(); + } } - // skip newlines + if (token != ']') { + throw createSyntaxError('End of matrix ] expected'); + } getToken(); - while (token == '\n') { - getToken(); + + // check if the number of columns matches in all rows + cols = (params.length > 0) ? params[0].length : 0; + for (var r = 1; r < rows; r++) { + if (params[r].length != cols) { + throw createError('Number of columns must match ' + + '(' + params[r].length + ' != ' + cols + ')'); + } } - params[r][c] = parseAssignment(scope); - - // skip newlines - while (token == '\n') { - getToken(); + array = new MatrixNode(params); + } + else { + // 1 dimensional vector + if (token != ']') { + throw createSyntaxError('End of matrix ] expected'); } + getToken(); + + array = row; } - - // TODO: spaces as separator for matrix columns - /* - // the columns in the matrix are separated by commas or spaces, - // and the rows by dot-comma's - while (token && token != ']') { - if (token == ';') { - r++; - c = 0; - params[r] = []; - getToken(); - } - else if (token == ',') { - c++; - getToken(); - } - else { - c++; - } - - // skip newlines - while (token == '\n') { - getToken(); - } - - //TODO: math.eval('[1 -2 3]') is evaluated as '[(1-2) 3]' instead of '[(1) (-2) (3)]' - //TODO: '[(1) (-2) (3)]' doesn't work - params[r][c] = parseAssignment(scope); - - // skip newlines - while (token == '\n') { - getToken(); - } - } - */ - - rows = params.length; - cols = (params.length > 0) ? params[0].length : 0; - - // check if the number of columns matches in all rows - for (r = 1; r < rows; r++) { - if (params[r].length != cols) { - throw createError('Number of columns must match ' + - '(' + params[r].length + ' != ' + cols + ')'); - } - } - - if (token != ']') { - throw createSyntaxError('End of matrix ] expected'); - } - - getToken(); - array = new MatrixNode(params); } else { // this is an empty matrix "[ ]" @@ -1159,6 +1125,36 @@ module.exports = function (math) { return parseNumber(scope); } + /** + * Parse a single comma-separated row from a matrix, like 'a, b, c' + * @param {Scope} scope + * @return {MatrixNode} node + */ + function parseRow (scope) { + var params = [parseAssignment(scope)]; + var len = 1; + + while (token == ',') { + getToken(); + + // skip newlines + while (token == '\n') { + getToken(); + } + + // parse expression + params[len] = parseAssignment(scope); + len++; + + // skip newlines + while (token == '\n') { + getToken(); + } + } + + return new MatrixNode(params); + } + /** * parse a number * @param {Scope} scope diff --git a/package.json b/package.json index 1ee01427e..7c6bf7e56 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mathjs", - "version": "0.13.1-SNAPSHOT", + "version": "0.14.0-SNAPSHOT", "description": "Math.js is an extensive math library for JavaScript and Node.js. It features real and complex numbers, units, matrices, a large set of mathematical functions, and a flexible expression parser.", "author": "Jos de Jong ", "contributors": [ diff --git a/test/function/expression/parse.test.js b/test/function/expression/parse.test.js index ec1f3cd45..98d4b1aff 100644 --- a/test/function/expression/parse.test.js +++ b/test/function/expression/parse.test.js @@ -155,7 +155,12 @@ describe('parse', function() { assert.deepEqual(b.size(), [2,2]); assert.deepEqual(b, new Matrix([[5,6],[1,1]])); + // from 1 to n dimensions assert.deepEqual(parseAndEval('[ ]'), new Matrix([])); + assert.deepEqual(parseAndEval('[1,2,3]'), new Matrix([1,2,3])); + assert.deepEqual(parseAndEval('[1;2;3]'), new Matrix([[1],[2],[3]])); + assert.deepEqual(parseAndEval('[[1,2],[3,4]]'), new Matrix([[1,2],[3,4]])); + assert.deepEqual(parseAndEval('[[[1],[2]],[[3],[4]]]'), new Matrix([[[1],[2]],[[3],[4]]])); }); @@ -189,15 +194,15 @@ describe('parse', function() { assert.deepEqual(parseAndEval('a = []', scope), new Matrix([])); assert.deepEqual(parseAndEval('a(1,3) = 3', scope), new Matrix([[0,0,3]])); - assert.deepEqual(parseAndEval('a(2,:) = [4,5,6]', scope), new Matrix([[0,0,3],[4,5,6]])); + assert.deepEqual(parseAndEval('a(2,:) = [[4,5,6]]', scope), new Matrix([[0,0,3],[4,5,6]])); assert.deepEqual(parseAndEval('a = []', scope), new Matrix([])); assert.deepEqual(parseAndEval('a(3,1) = 3', scope), new Matrix([[0],[0],[3]])); assert.deepEqual(parseAndEval('a(:,2) = [4;5;6]', scope), new Matrix([[0,4],[0,5],[3,6]])); assert.deepEqual(parseAndEval('a = []', scope), new Matrix([])); - assert.deepEqual(parseAndEval('a(1,1:3) = [1,2,3]', scope), new Matrix([[1,2,3]])); - assert.deepEqual(parseAndEval('a(2,:) = [4,5,6]', scope), new Matrix([[1,2,3],[4,5,6]])); + assert.deepEqual(parseAndEval('a(1,1:3) = [[1,2,3]]', scope), new Matrix([[1,2,3]])); + assert.deepEqual(parseAndEval('a(2,:) = [[4,5,6]]', scope), new Matrix([[1,2,3],[4,5,6]])); }); it('should get/set the matrix correctly', function() { @@ -260,29 +265,33 @@ describe('parse', function() { assert.deepEqual(parseAndEval('a(2:end-1, 2:end-1)', scope), new Matrix([[2,0],[9,9]])); }); + it('should merge nested matrices', function() { + var scope = {}; + parseAndEval('a=[1,2;3,4]', scope); + + }); + it('should parse matrix concatenations', function() { var scope = {}; parseAndEval('a=[1,2;3,4]', scope); parseAndEval('b=[5,6;7,8]', scope); - assert.deepEqual(parseAndEval('c=[a,b]', scope), new Matrix([[1,2,5,6],[3,4,7,8]])); - assert.deepEqual(parseAndEval('c=[a;b]', scope), new Matrix([[1,2],[3,4],[5,6],[7,8]])); - assert.deepEqual(parseAndEval('c=[a,b;b,a]', scope), new Matrix([[1,2,5,6],[3,4,7,8],[5,6,1,2],[7,8,3,4]])); - assert.deepEqual(parseAndEval('c=[[1,2]; [3,4]]', scope), new Matrix([[1,2],[3,4]])); - assert.deepEqual(parseAndEval('c=[1; [2;3]]', scope), new Matrix([[1],[2],[3]])); + assert.deepEqual(parseAndEval('c=concat(a,b)', scope), new Matrix([[1,2,5,6],[3,4,7,8]])); + assert.deepEqual(parseAndEval('c=concat(a,b,0)', scope), new Matrix([[1,2],[3,4],[5,6],[7,8]])); + assert.deepEqual(parseAndEval('c=concat(concat(a,b), concat(b,a), 0)', scope), new Matrix([[1,2,5,6],[3,4,7,8],[5,6,1,2],[7,8,3,4]])); + assert.deepEqual(parseAndEval('c=concat([[1,2]], [[3,4]], 0)', scope), new Matrix([[1,2],[3,4]])); + assert.deepEqual(parseAndEval('c=concat([[1]], [2;3], 0)', scope), new Matrix([[1],[2],[3]])); assert.deepEqual(parseAndEval('d=1:3', scope), [1,2,3]); - assert.deepEqual(parseAndEval('[d,d]', scope), new Matrix([[1,2,3,1,2,3]])); - assert.deepEqual(parseAndEval('[d;d]', scope), new Matrix([[1,2,3],[1,2,3]])); + assert.deepEqual(parseAndEval('concat(d,d)', scope), [1,2,3,1,2,3]); assert.deepEqual(parseAndEval('e=1+d', scope), [2,3,4]); // e is an Array assert.deepEqual(parseAndEval('size(e)', scope), [3]); - assert.deepEqual(parseAndEval('[e,e]', scope), new Matrix([[2,3,4,2,3,4]])); - assert.deepEqual(parseAndEval('[e;e]', scope), new Matrix([[2,3,4],[2,3,4]])); - assert.deepEqual(parseAndEval('[[],[]]', scope), new Matrix([[]])); - assert.deepEqual(parseAndEval('[[],[]]', scope).size(), [1, 0]); + assert.deepEqual(parseAndEval('concat(e,e)', scope), [2,3,4,2,3,4]); + assert.deepEqual(parseAndEval('[[],[]]', scope), new Matrix([[],[]])); + assert.deepEqual(parseAndEval('[[],[]]', scope).size(), [2, 0]); }); it('should throw an error for invalid matrix concatenations', function() { var scope = {}; - assert.throws(function () {parseAndEval('c=[a; [1,2,3] ]', scope)}); + assert.throws(function () {parseAndEval('c=concat(a, [1,2,3])', scope)}); }); }); @@ -332,7 +341,7 @@ describe('parse', function() { assert.equal(scope.c, 4.5); assert.equal(scope.d, 4.5); assert.equal(scope.e, 4.5); - assert.deepEqual(parseAndEval('a = [1,2,f=3]', scope), new Matrix([[1,2,3]])); + assert.deepEqual(parseAndEval('a = [1,2,f=3]', scope), new Matrix([1,2,3])); assert.equal(scope.f, 3); assert.equal(parseAndEval('2 + (g = 3 + 4)', scope), 9); assert.equal(scope.g, 7); @@ -425,7 +434,7 @@ describe('parse', function() { assert.equal(parseAndEval('4 ./ 2'), 2); assert.equal(parseAndEval('8 ./ 2 / 2'), 2); - assert.deepEqual(parseAndEval('[1,2,3] ./ [1,2,3]'), new Matrix([[1,1,1]])); + assert.deepEqual(parseAndEval('[1,2,3] ./ [1,2,3]'), new Matrix([1,1,1])); }); it('should parse .*', function() { @@ -436,7 +445,7 @@ describe('parse', function() { approx.deepEqual(parseAndEval('8 .* 2 .* 2'), 32); assert.deepEqual(parseAndEval('a=3; a.*4'), [12]); - assert.deepEqual(parseAndEval('[1,2,3] .* [1,2,3]'), new Matrix([[1,4,9]])); + assert.deepEqual(parseAndEval('[1,2,3] .* [1,2,3]'), new Matrix([1,4,9])); }); it('should parse .^', function() { @@ -446,7 +455,7 @@ describe('parse', function() { approx.deepEqual(parseAndEval('-2.^2'), -4); // -(2^2) approx.deepEqual(parseAndEval('2.^3.^4'), 2.41785163922926e+24); // 2^(3^4) - assert.deepEqual(parseAndEval('[2,3] .^ [2,3]'), new Matrix([[4,27]])); + assert.deepEqual(parseAndEval('[2,3] .^ [2,3]'), new Matrix([4,27])); }); it('should parse ==', function() { @@ -640,7 +649,7 @@ describe('parse', function() { it('should correctly stringify a node tree', function() { assert.equal(math.parse('0').toString(), 'ans = 0'); assert.equal(math.parse('"hello"').toString(), 'ans = "hello"'); - assert.equal(math.parse('[1, 2 + 3i, 4]').toString(), 'ans = [[1, 2 + 3i, 4]]'); + assert.equal(math.parse('[1, 2 + 3i, 4]').toString(), 'ans = [1, 2 + 3i, 4]'); }); it('should support custom node handlers', function() {