From 485ebb3c9d457753e4333fd9bc3674416653012d Mon Sep 17 00:00:00 2001 From: Mahdi Dibaiee Date: Sun, 21 Jun 2015 23:32:12 +0430 Subject: [PATCH] =?UTF-8?q?Added=20Sigma=20operator=20=E2=80=93=20support?= =?UTF-8?q?=20for=20more=20complex=20operators=20:star2:=20Fix=20README.md?= =?UTF-8?q?=20links=20pointing=20to=20Equation.js=20instead=20of=20Equatio?= =?UTF-8?q?n=20Bump=20version=20to=201.3.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 11 ++++--- dist/index.js | 33 +++++++++++++++----- dist/operators.js | 29 +++++++++++++++++ dist/tests/solve.js | 20 +++++++++++- equation.js | 76 +++++++++++++++++++++++++++++++++++++++------ equation.min.js | 2 +- lib/index.js | 33 +++++++++++++++----- lib/operators.js | 24 ++++++++++++++ package.json | 2 +- tests/solve.js | 20 +++++++++++- 10 files changed, 219 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index ddc3b81..ab872dd 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -Equation [![Build Status](https://travis-ci.org/mdibaiee/Equation.js.svg?branch=master)](https://travis-ci.org/mdibaiee/Equation.js) +Equation [![Build Status](https://travis-ci.org/mdibaiee/Equation.svg?branch=master)](https://travis-ci.org/mdibaiee/Equation.js) ======== Solve math expressions or create equations for repeated and complex Math tasks. -To use in browser, download [`equation.min.js`](https://raw.githubusercontent.com/mdibaiee/Equation.js/master/equation.min.js). +To use in browser, download [`equation.min.js`](https://raw.githubusercontent.com/mdibaiee/Equation/master/equation.min.js). Install using npm: @@ -27,7 +27,7 @@ console.log(test(4, 3)).to.equal(8 + 18); You can also register your own operators and constants. ```javascript -// these options are explained below +// these options are explained in [`operators.js`](https://github.com/mdibaiee/Equation/blob/master/lib/operators.js). Equation.registerOperator('$', { fn: a => 1/a, format: '10', @@ -42,9 +42,12 @@ Equation.registerConstant('N', () => { }); Equation.solve('N'); // a number between 10 and 20 + +// Complex operators +Equation.solve('sigma(0, 5, 2@ + 5)'); // 60 ``` -For a list of operators and constants see [`operators.js`](https://github.com/mdibaiee/Equation.js/blob/master/lib/operators.js) and [`constants.js`](https://github.com/mdibaiee/Equation.js/blob/master/lib/constants.js). +For a list of operators and constants see [`operators.js`](https://github.com/mdibaiee/Equation/blob/master/lib/operators.js) and [`constants.js`](https://github.com/mdibaiee/Equation/blob/master/lib/constants.js). API === diff --git a/dist/index.js b/dist/index.js index f5e9261..4aba796 100644 --- a/dist/index.js +++ b/dist/index.js @@ -107,6 +107,13 @@ var Equation = { }; }, + // 2x + 5x * x => 7x^2 + simplify: function simplify(expression) { + var stack = parseExpression(expression); + + console.log(stack); + }, + registerOperator: function registerOperator(key, options) { _operators2['default'][key] = options; }, @@ -127,7 +134,13 @@ var solveStack = (function (_solveStack) { return solveStack; })(function (stack) { - if (stack.some(Array.isArray)) { + // if an operator takes an expression argument, we should not dive into it + // and solve the expression inside + var hasExpressionArgument = stack.some(function (a) { + return _operators2['default'][a] && _operators2['default'][a].hasExpression; + }); + + if (!hasExpressionArgument && stack.some(Array.isArray)) { stack = stack.map(function (group) { if (!Array.isArray(group)) { return group; @@ -159,6 +172,12 @@ var MIN_PRECEDENCE = Math.min.apply(Math, _toConsumableArray(PRECEDENCES)); * The parsed array */ var parseExpression = function parseExpression(expression) { + // function arguments can be separated using comma, + // but we parse as groups, so this is the solution to getting comma to work + // sigma(0, 4, 2@) becomes sigma(0)(4)(2@) so every argument is parsed + // separately + expression = expression.replace(/,/g, ')('); + var stream = new _ReadStream2['default'](expression), stack = [], record = '', @@ -238,19 +257,19 @@ var parseExpression = function parseExpression(expression) { */ var parseGroups = function parseGroups(stack) { // Parantheses become inner arrays which will then be processed first - var sub = 0; + var depth = 0; return stack.reduce(function (a, b) { if (b.indexOf('(') > -1) { if (b.length > 1) { - _.dive(a, sub).push(b.replace('(', ''), []); + _.dive(a, depth).push(b.replace('(', ''), []); } else { - _.dive(a, sub).push([]); + _.dive(a, depth).push([]); } - sub++; + depth++; } else if (b === ')') { - sub--; + depth--; } else { - _.dive(a, sub).push(b); + _.dive(a, depth).push(b); } return a; }, []); diff --git a/dist/operators.js b/dist/operators.js index b5457ab..dfb3125 100644 --- a/dist/operators.js +++ b/dist/operators.js @@ -1,8 +1,15 @@ 'use strict'; +var _interopRequireWildcard = function (obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }; + Object.defineProperty(exports, '__esModule', { value: true }); + +var _Equation = require('equation'); + +var _Equation2 = _interopRequireWildcard(_Equation); + /* * Operators and Functions * fn: function used to evaluate value @@ -11,6 +18,11 @@ Object.defineProperty(exports, '__esModule', { * e.g: factorial is 01, add is 010, like 2!, 2+2 * precedence: determines which operators should be evaluated first * the lower the value, the higher the precedence + * hasExpression: determines if any of the operator arguments are an expression + * This way, arguments will not be solved by equation and instead + * you have to call solve on each argument yourself. + * You get the arguments as parsed arrays sigma(0, 5, 2@) becomes + * sigma(0, 5, [2, '*', '@']). See sigma operator for reference */ exports['default'] = { '^': { @@ -119,6 +131,23 @@ exports['default'] = { fn: Math.floor, format: '10', precedence: -1 + }, + sigma: { + fn: function fn(from, to, expression) { + var expr = expression.join('').replace(/,/g, ''); + var regex = new RegExp(ITERATOR_SIGN, 'g'); + + var sum = 0; + for (var i = from; i <= to; i++) { + sum += _Equation2['default'].solve(expr.replace(regex, i)); + } + return sum; + }, + format: '1000', + hasExpression: true, + precedence: -1 } }; + +var ITERATOR_SIGN = '@'; module.exports = exports['default']; \ No newline at end of file diff --git a/dist/tests/solve.js b/dist/tests/solve.js index b6a088b..d10e6cd 100644 --- a/dist/tests/solve.js +++ b/dist/tests/solve.js @@ -92,7 +92,25 @@ describe('Constats', function () { _expect.expect(_M2['default'].solve('sin(PI/2)')).to.equal(1); }); - it('should work for functions as constants', function () { + it('should work for functions as constants - retry on fail', function () { _expect.expect(_M2['default'].solve('RAND')).to.not.equal(_M2['default'].solve('RAND')); }); +}); + +describe('Sigma', function () { + it('should work with simple expressions', function () { + _expect.expect(_M2['default'].solve('sigma(0, 5, @)')).to.equal(15); + }); + + it('should work with more complex expressions', function () { + _expect.expect(_M2['default'].solve('sigma(0, 2, 2@+5)')).to.equal(21); + }); + + it('should work without an iterator sign', function () { + _expect.expect(_M2['default'].solve('sigma(0, 2, 5*2)')).to.equal(30); + }); + + it('should work with negative start / end points', function () { + _expect.expect(_M2['default'].solve('sigma(-5, -2, @)')).to.equal(-14); + }); }); \ No newline at end of file diff --git a/equation.js b/equation.js index 38f3459..15c95de 100644 --- a/equation.js +++ b/equation.js @@ -284,6 +284,13 @@ var Equation = { }; }, + // 2x + 5x * x => 7x^2 + simplify: function simplify(expression) { + var stack = parseExpression(expression); + + console.log(stack); + }, + registerOperator: function registerOperator(key, options) { _operators2['default'][key] = options; }, @@ -304,7 +311,13 @@ var solveStack = (function (_solveStack) { return solveStack; })(function (stack) { - if (stack.some(Array.isArray)) { + // if an operator takes an expression argument, we should not dive into it + // and solve the expression inside + var hasExpressionArgument = stack.some(function (a) { + return _operators2['default'][a] && _operators2['default'][a].hasExpression; + }); + + if (!hasExpressionArgument && stack.some(Array.isArray)) { stack = stack.map(function (group) { if (!Array.isArray(group)) { return group; @@ -336,6 +349,12 @@ var MIN_PRECEDENCE = Math.min.apply(Math, _toConsumableArray(PRECEDENCES)); * The parsed array */ var parseExpression = function parseExpression(expression) { + // function arguments can be separated using comma, + // but we parse as groups, so this is the solution to getting comma to work + // sigma(0, 4, 2@) becomes sigma(0)(4)(2@) so every argument is parsed + // separately + expression = expression.replace(/,/g, ')('); + var stream = new _ReadStream2['default'](expression), stack = [], record = '', @@ -415,19 +434,19 @@ var parseExpression = function parseExpression(expression) { */ var parseGroups = function parseGroups(stack) { // Parantheses become inner arrays which will then be processed first - var sub = 0; + var depth = 0; return stack.reduce(function (a, b) { if (b.indexOf('(') > -1) { if (b.length > 1) { - _.dive(a, sub).push(b.replace('(', ''), []); + _.dive(a, depth).push(b.replace('(', ''), []); } else { - _.dive(a, sub).push([]); + _.dive(a, depth).push([]); } - sub++; + depth++; } else if (b === ')') { - sub--; + depth--; } else { - _.dive(a, sub).push(b); + _.dive(a, depth).push(b); } return a; }, []); @@ -650,9 +669,16 @@ exports['default'] = Equation; },{"./constants":1,"./helpers":2,"./operators":4,"./readstream":5}],4:[function(require,module,exports){ 'use strict'; +var _interopRequireWildcard = function (obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }; + Object.defineProperty(exports, '__esModule', { value: true }); + +var _Equation = require('equation'); + +var _Equation2 = _interopRequireWildcard(_Equation); + /* * Operators and Functions * fn: function used to evaluate value @@ -661,6 +687,11 @@ Object.defineProperty(exports, '__esModule', { * e.g: factorial is 01, add is 010, like 2!, 2+2 * precedence: determines which operators should be evaluated first * the lower the value, the higher the precedence + * hasExpression: determines if any of the operator arguments are an expression + * This way, arguments will not be solved by equation and instead + * you have to call solve on each argument yourself. + * You get the arguments as parsed arrays sigma(0, 5, 2@) becomes + * sigma(0, 5, [2, '*', '@']). See sigma operator for reference */ exports['default'] = { '^': { @@ -769,10 +800,27 @@ exports['default'] = { fn: Math.floor, format: '10', precedence: -1 + }, + sigma: { + fn: function fn(from, to, expression) { + var expr = expression.join('').replace(/,/g, ''); + var regex = new RegExp(ITERATOR_SIGN, 'g'); + + var sum = 0; + for (var i = from; i <= to; i++) { + sum += _Equation2['default'].solve(expr.replace(regex, i)); + } + return sum; + }, + format: '1000', + hasExpression: true, + precedence: -1 } }; + +var ITERATOR_SIGN = '@'; module.exports = exports['default']; -},{}],5:[function(require,module,exports){ +},{"equation":8}],5:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, '__esModule', { @@ -835,5 +883,15 @@ exports['default'] = function (string) { }; module.exports = exports['default']; -},{}]},{},[3])(3) +},{}],6:[function(require,module,exports){ +arguments[4][1][0].apply(exports,arguments) +},{"dup":1}],7:[function(require,module,exports){ +arguments[4][2][0].apply(exports,arguments) +},{"dup":2}],8:[function(require,module,exports){ +arguments[4][3][0].apply(exports,arguments) +},{"./constants":6,"./helpers":7,"./operators":9,"./readstream":10,"dup":3}],9:[function(require,module,exports){ +arguments[4][4][0].apply(exports,arguments) +},{"dup":4,"equation":8}],10:[function(require,module,exports){ +arguments[4][5][0].apply(exports,arguments) +},{"dup":5}]},{},[3])(3) }); \ No newline at end of file diff --git a/equation.min.js b/equation.min.js index 92c5bfe..9e9fda3 100644 --- a/equation.min.js +++ b/equation.min.js @@ -1 +1 @@ -!function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;b="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,b.Equation=a()}}(function(){return function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;gd;++d)c=c[c.length-1];return c};c.dive=g;var h=function(a){function b(b,c){return a.apply(this,arguments)}return b.toString=function(){return a.toString()},b}(function(a,b){var c=void 0===arguments[2]?0:arguments[2];if(2>b)return{arr:a,index:c};var d=a.reduce(function(a,d,e){if(Array.isArray(d)){var f=h(d,b-1,e),g=f.arr,i=f.index,j=a.concat(g);return c=i,j}return a},[]);return{arr:d,index:c}});c.deep=h;var i=function(a){function b(b,c,d){return a.apply(this,arguments)}return b.toString=function(){return a.toString()},b}(function(a,b,c){var d=[];if(!b.some(Array.isArray))return a[b[0]]=c,c;var e=!0,f=!1,g=void 0;try{for(var h,j=b[Symbol.iterator]();!(e=(h=j.next()).done);e=!0){var k=h.value;d.push(i(a,k,c))}}catch(l){f=!0,g=l}finally{try{!e&&j["return"]&&j["return"]()}finally{if(f)throw g}}return d});c.diveTo=i;var j=function(a){function b(b){return a.apply(this,arguments)}return b.toString=function(){return a.toString()},b}(function(a){return Array.isArray(a)&&a.some(Array.isArray)?a.reduce(function(a,b){return a.concat(j(b))},[]):a});c.flatten=j;var k=function(a){return a.toString().replace(/\W/g,"")};c.removeSymbols=k},{}],3:[function(a,b,c){"use strict";var d=function(a){return a&&a.__esModule?a:{"default":a}},e=function(a,b){if(Array.isArray(a))return a;if(Symbol.iterator in Object(a)){var c=[],d=!0,e=!1,f=void 0;try{for(var g,h=a[Symbol.iterator]();!(d=(g=h.next()).done)&&(c.push(g.value),!b||c.length!==b);d=!0);}catch(i){e=!0,f=i}finally{try{!d&&h["return"]&&h["return"]()}finally{if(e)throw f}}return c}throw new TypeError("Invalid attempt to destructure non-iterable instance")},f=function(a){if(Array.isArray(a)){for(var b=0,c=Array(a.length);be;e++)d[e]=arguments[e];return b.forEach(function f(a,b,e){if(Array.isArray(a))return a.forEach(f);var g=c.indexOf(a);g>-1&&(e[b]=d[g])}),b=w(b),b=n.parseNumbers(b),b=p(b)}},registerOperator:function(a,b){j["default"][a]=b},registerConstant:function(a,b){l["default"][a]=b}},p=function(a){function b(b){return a.apply(this,arguments)}return b.toString=function(){return a.toString()},b}(function(a){return a.some(Array.isArray)?(a=a.map(function(a){return Array.isArray(a)?p(a):a}),p(a)):x(a)}),q=Object.keys(j["default"]).map(function(a){return j["default"][a].precedence}),r=Math.max.apply(Math,f(q)),s=Math.min.apply(Math,f(q)),t=function(a){for(var b=new h["default"](a),c=[],d="",e=void 0,f=void 0;b.next();)if(e=b.current(),f=c.length-1," "!==e)if(n.isNumber(e)||j["default"][e]||"."===e||"("===e||")"===e)if(d.length){var g=f-(d.length-1);B(d)&&n.isNumber(c[g])&&c.push("*"),c.push(d,e),d=""}else if(n.isNumber(c[f])&&(n.isNumber(e)||"."===e))c[f]+=e;else if("-"===c[f]){var i=c[f-1];j["default"][i]?c[f]+=e:")"===i?(c[f]="+",c.push("-"+e)):n.isNumber(i)?c.push(e):c[f]+=e}else c.push(e);else d+=e;if(d.length){var g=f-(d.length-1);B(d)&&n.isNumber(c[g])&&c.push("*"),c.push(d)}return u(c)},u=function(a){var b=0;return a.reduce(function(a,c){return c.indexOf("(")>-1?(c.length>1?n.dive(a,b).push(c.replace("(",""),[]):n.dive(a,b).push([]),b++):")"===c?b--:n.dive(a,b).push(c),a},[])},v=function(a){var b="string"==typeof a?j["default"][a]:a;if(!b)return null;var c=b.format.split("1"),d=c[0].length,e=c[1].length;return{left:d,right:e}},w=function(a){function b(b){return a.apply(this,arguments)}return b.toString=function(){return a.toString()},b}(function(a){var b=!0,c=!1,d=void 0;try{for(var f,g=a.entries()[Symbol.iterator]();!(b=(f=g.next()).done);b=!0){var h=e(f.value,2),i=h[0],k=h[1];Array.isArray(k)&&a.splice(i,1,w(k))}}catch(l){c=!0,d=l}finally{try{!b&&g["return"]&&g["return"]()}finally{if(c)throw d}}for(var m=s;r>=m;m++)for(var i=0;iu;u++)t=[t];i-=q}}return a}),x=function(a){var b,c=y(a);if(!c)return a[0];var d=v(c),e=d.left,g=a.slice(0,e),h=a.slice(e+1);return A((b=j["default"][c]).fn.apply(b,f(g).concat(f(h))))},y=function(a){var b=!0,c=!1,d=void 0;try{for(var e,f=a[Symbol.iterator]();!(b=(e=f.next()).done);b=!0){var g=e.value;if("string"==typeof g)return g}}catch(h){c=!0,d=h}finally{try{!b&&f["return"]&&f["return"]()}finally{if(c)throw d}}return null},z=function(a){return a.replace(/[A-Z]*/g,function(a){var b=l["default"][a];return b?"function"==typeof b?b():b:a})},A=function(a){return+a.toFixed(15)},B=function(a){return"string"==typeof a&&!n.isNumber(a)&&!j["default"][a]&&a===a.toLowerCase()};c.isVariable=B,c["default"]=o},{"./constants":1,"./helpers":2,"./operators":4,"./readstream":5}],4:[function(a,b,c){"use strict";Object.defineProperty(c,"__esModule",{value:!0}),c["default"]={"^":{fn:function(a,b){return Math.pow(a,b)},format:"010",precedence:0},"*":{fn:function(a,b){return a*b},format:"010",precedence:1},"/":{fn:function(a,b){return a/b},format:"010",precedence:1},"%":{fn:function(a,b){return a%b},format:"010",precedence:1},"\\":{fn:function(a,b){return Math.floor(a/b)},format:"010",precedence:1},"+":{fn:function(a,b){return a+b},format:"010",precedence:2},"-":{fn:function(a,b){return a-b},format:"010",precedence:2},"!":{fn:function(a){for(var b=1,c=0;a>c;++c)b*=c;return b},format:"01",precedence:2},log:{fn:Math.log,format:"10",precedence:-1},ln:{fn:Math.log,format:"10",precedence:-1},lg:{fn:function(a){return Math.log(a)/Math.log(2)},format:"10",precedence:-1},sin:{fn:Math.sin,format:"10",precedence:-1},cos:{fn:Math.cos,format:"10",precedence:-1},tan:{fn:Math.tan,format:"10",precedence:-1},cot:{fn:Math.cot,format:"10",precedence:-1},round:{fn:Math.round,format:"10",precedence:-1},floor:{fn:Math.floor,format:"10",precedence:-1}},b.exports=c["default"]},{}],5:[function(a,b,c){"use strict";Object.defineProperty(c,"__esModule",{value:!0}),c["default"]=function(a){var b=0,c=[];return{next:function(){return c.push(a[b]),b>=a.length?null:a[b++]},current:function(){return a[b-1]},index:function(){return b-1},to:function(c){var d="",e=b+c;for(b=b;e>b;++b)d+=[a[b]];return d},drain:function(){return c.splice(0,c.length)},replace:function(a){function b(b,c,d){return a.apply(this,arguments)}return b.toString=function(){return a.toString()},b}(function(c,d,e){var f=a.split("");f.splice(c,d,e),a=f.join(""),b-=d-c}),go:function(a){b+=a},all:function(){return a}}},b.exports=c["default"]},{}]},{},[3])(3)}); \ No newline at end of file +!function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;b="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,b.Equation=a()}}(function(){return function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;gd;++d)c=c[c.length-1];return c};c.dive=g;var h=function(a){function b(b,c){return a.apply(this,arguments)}return b.toString=function(){return a.toString()},b}(function(a,b){var c=void 0===arguments[2]?0:arguments[2];if(2>b)return{arr:a,index:c};var d=a.reduce(function(a,d,e){if(Array.isArray(d)){var f=h(d,b-1,e),g=f.arr,i=f.index,j=a.concat(g);return c=i,j}return a},[]);return{arr:d,index:c}});c.deep=h;var i=function(a){function b(b,c,d){return a.apply(this,arguments)}return b.toString=function(){return a.toString()},b}(function(a,b,c){var d=[];if(!b.some(Array.isArray))return a[b[0]]=c,c;var e=!0,f=!1,g=void 0;try{for(var h,j=b[Symbol.iterator]();!(e=(h=j.next()).done);e=!0){var k=h.value;d.push(i(a,k,c))}}catch(l){f=!0,g=l}finally{try{!e&&j["return"]&&j["return"]()}finally{if(f)throw g}}return d});c.diveTo=i;var j=function(a){function b(b){return a.apply(this,arguments)}return b.toString=function(){return a.toString()},b}(function(a){return Array.isArray(a)&&a.some(Array.isArray)?a.reduce(function(a,b){return a.concat(j(b))},[]):a});c.flatten=j;var k=function(a){return a.toString().replace(/\W/g,"")};c.removeSymbols=k},{}],3:[function(a,b,c){"use strict";var d=function(a){return a&&a.__esModule?a:{"default":a}},e=function(a,b){if(Array.isArray(a))return a;if(Symbol.iterator in Object(a)){var c=[],d=!0,e=!1,f=void 0;try{for(var g,h=a[Symbol.iterator]();!(d=(g=h.next()).done)&&(c.push(g.value),!b||c.length!==b);d=!0);}catch(i){e=!0,f=i}finally{try{!d&&h["return"]&&h["return"]()}finally{if(e)throw f}}return c}throw new TypeError("Invalid attempt to destructure non-iterable instance")},f=function(a){if(Array.isArray(a)){for(var b=0,c=Array(a.length);be;e++)d[e]=arguments[e];return b.forEach(function f(a,b,e){if(Array.isArray(a))return a.forEach(f);var g=c.indexOf(a);g>-1&&(e[b]=d[g])}),b=w(b),b=n.parseNumbers(b),b=p(b)}},simplify:function(a){var b=t(a);console.log(b)},registerOperator:function(a,b){j["default"][a]=b},registerConstant:function(a,b){l["default"][a]=b}},p=function(a){function b(b){return a.apply(this,arguments)}return b.toString=function(){return a.toString()},b}(function(a){var b=a.some(function(a){return j["default"][a]&&j["default"][a].hasExpression});return!b&&a.some(Array.isArray)?(a=a.map(function(a){return Array.isArray(a)?p(a):a}),p(a)):x(a)}),q=Object.keys(j["default"]).map(function(a){return j["default"][a].precedence}),r=Math.max.apply(Math,f(q)),s=Math.min.apply(Math,f(q)),t=function(a){a=a.replace(/,/g,")(");for(var b=new h["default"](a),c=[],d="",e=void 0,f=void 0;b.next();)if(e=b.current(),f=c.length-1," "!==e)if(n.isNumber(e)||j["default"][e]||"."===e||"("===e||")"===e)if(d.length){var g=f-(d.length-1);B(d)&&n.isNumber(c[g])&&c.push("*"),c.push(d,e),d=""}else if(n.isNumber(c[f])&&(n.isNumber(e)||"."===e))c[f]+=e;else if("-"===c[f]){var i=c[f-1];j["default"][i]?c[f]+=e:")"===i?(c[f]="+",c.push("-"+e)):n.isNumber(i)?c.push(e):c[f]+=e}else c.push(e);else d+=e;if(d.length){var g=f-(d.length-1);B(d)&&n.isNumber(c[g])&&c.push("*"),c.push(d)}return u(c)},u=function(a){var b=0;return a.reduce(function(a,c){return c.indexOf("(")>-1?(c.length>1?n.dive(a,b).push(c.replace("(",""),[]):n.dive(a,b).push([]),b++):")"===c?b--:n.dive(a,b).push(c),a},[])},v=function(a){var b="string"==typeof a?j["default"][a]:a;if(!b)return null;var c=b.format.split("1"),d=c[0].length,e=c[1].length;return{left:d,right:e}},w=function(a){function b(b){return a.apply(this,arguments)}return b.toString=function(){return a.toString()},b}(function(a){var b=!0,c=!1,d=void 0;try{for(var f,g=a.entries()[Symbol.iterator]();!(b=(f=g.next()).done);b=!0){var h=e(f.value,2),i=h[0],k=h[1];Array.isArray(k)&&a.splice(i,1,w(k))}}catch(l){c=!0,d=l}finally{try{!b&&g["return"]&&g["return"]()}finally{if(c)throw d}}for(var m=s;r>=m;m++)for(var i=0;iu;u++)t=[t];i-=q}}return a}),x=function(a){var b,c=y(a);if(!c)return a[0];var d=v(c),e=d.left,g=a.slice(0,e),h=a.slice(e+1);return A((b=j["default"][c]).fn.apply(b,f(g).concat(f(h))))},y=function(a){var b=!0,c=!1,d=void 0;try{for(var e,f=a[Symbol.iterator]();!(b=(e=f.next()).done);b=!0){var g=e.value;if("string"==typeof g)return g}}catch(h){c=!0,d=h}finally{try{!b&&f["return"]&&f["return"]()}finally{if(c)throw d}}return null},z=function(a){return a.replace(/[A-Z]*/g,function(a){var b=l["default"][a];return b?"function"==typeof b?b():b:a})},A=function(a){return+a.toFixed(15)},B=function(a){return"string"==typeof a&&!n.isNumber(a)&&!j["default"][a]&&a===a.toLowerCase()};c.isVariable=B,c["default"]=o},{"./constants":1,"./helpers":2,"./operators":4,"./readstream":5}],4:[function(a,b,c){"use strict";var d=function(a){return a&&a.__esModule?a:{"default":a}};Object.defineProperty(c,"__esModule",{value:!0});var e=a("equation"),f=d(e);c["default"]={"^":{fn:function(a,b){return Math.pow(a,b)},format:"010",precedence:0},"*":{fn:function(a,b){return a*b},format:"010",precedence:1},"/":{fn:function(a,b){return a/b},format:"010",precedence:1},"%":{fn:function(a,b){return a%b},format:"010",precedence:1},"\\":{fn:function(a,b){return Math.floor(a/b)},format:"010",precedence:1},"+":{fn:function(a,b){return a+b},format:"010",precedence:2},"-":{fn:function(a,b){return a-b},format:"010",precedence:2},"!":{fn:function(a){for(var b=1,c=0;a>c;++c)b*=c;return b},format:"01",precedence:2},log:{fn:Math.log,format:"10",precedence:-1},ln:{fn:Math.log,format:"10",precedence:-1},lg:{fn:function(a){return Math.log(a)/Math.log(2)},format:"10",precedence:-1},sin:{fn:Math.sin,format:"10",precedence:-1},cos:{fn:Math.cos,format:"10",precedence:-1},tan:{fn:Math.tan,format:"10",precedence:-1},cot:{fn:Math.cot,format:"10",precedence:-1},round:{fn:Math.round,format:"10",precedence:-1},floor:{fn:Math.floor,format:"10",precedence:-1},sigma:{fn:function(a,b,c){for(var d=c.join("").replace(/,/g,""),e=new RegExp(g,"g"),h=0,i=a;b>=i;i++)h+=f["default"].solve(d.replace(e,i));return h},format:"1000",hasExpression:!0,precedence:-1}};var g="@";b.exports=c["default"]},{equation:8}],5:[function(a,b,c){"use strict";Object.defineProperty(c,"__esModule",{value:!0}),c["default"]=function(a){var b=0,c=[];return{next:function(){return c.push(a[b]),b>=a.length?null:a[b++]},current:function(){return a[b-1]},index:function(){return b-1},to:function(c){var d="",e=b+c;for(b=b;e>b;++b)d+=[a[b]];return d},drain:function(){return c.splice(0,c.length)},replace:function(a){function b(b,c,d){return a.apply(this,arguments)}return b.toString=function(){return a.toString()},b}(function(c,d,e){var f=a.split("");f.splice(c,d,e),a=f.join(""),b-=d-c}),go:function(a){b+=a},all:function(){return a}}},b.exports=c["default"]},{}],6:[function(a,b,c){arguments[4][1][0].apply(c,arguments)},{dup:1}],7:[function(a,b,c){arguments[4][2][0].apply(c,arguments)},{dup:2}],8:[function(a,b,c){arguments[4][3][0].apply(c,arguments)},{"./constants":6,"./helpers":7,"./operators":9,"./readstream":10,dup:3}],9:[function(a,b,c){arguments[4][4][0].apply(c,arguments)},{dup:4,equation:8}],10:[function(a,b,c){arguments[4][5][0].apply(c,arguments)},{dup:5}]},{},[3])(3)}); \ No newline at end of file diff --git a/lib/index.js b/lib/index.js index c09eb71..3dd45ab 100644 --- a/lib/index.js +++ b/lib/index.js @@ -80,6 +80,13 @@ let Equation = { }; }, + // 2x + 5x * x => 7x^2 + simplify(expression) { + let stack = parseExpression(expression); + + console.log(stack); + }, + registerOperator(key, options) { operators[key] = options; }, @@ -90,7 +97,13 @@ let Equation = { }; const solveStack = stack => { - if (stack.some(Array.isArray)) { + // if an operator takes an expression argument, we should not dive into it + // and solve the expression inside + const hasExpressionArgument = stack.some(a => { + return operators[a] && operators[a].hasExpression; + }); + + if (!hasExpressionArgument && stack.some(Array.isArray)) { stack = stack.map(group => { if (!Array.isArray(group)) { return group; @@ -122,6 +135,12 @@ const MIN_PRECEDENCE = Math.min(...PRECEDENCES); * The parsed array */ const parseExpression = expression => { + // function arguments can be separated using comma, + // but we parse as groups, so this is the solution to getting comma to work + // sigma(0, 4, 2@) becomes sigma(0)(4)(2@) so every argument is parsed + // separately + expression = expression.replace(/,/g, ')('); + let stream = new ReadStream(expression), stack = [], record = '', @@ -202,19 +221,19 @@ const parseExpression = expression => { */ const parseGroups = stack => { // Parantheses become inner arrays which will then be processed first - let sub = 0; + let depth = 0; return stack.reduce((a, b) => { if (b.indexOf('(') > -1) { if (b.length > 1) { - _.dive(a, sub).push(b.replace('(', ''), []); + _.dive(a, depth).push(b.replace('(', ''), []); } else { - _.dive(a, sub).push([]); + _.dive(a, depth).push([]); } - sub++; + depth++; } else if (b === ')') { - sub--; + depth--; } else { - _.dive(a, sub).push(b); + _.dive(a, depth).push(b); } return a; }, []); diff --git a/lib/operators.js b/lib/operators.js index 1005bfd..7621e51 100644 --- a/lib/operators.js +++ b/lib/operators.js @@ -1,3 +1,5 @@ +import Equation from 'equation'; + /* * Operators and Functions * fn: function used to evaluate value @@ -6,6 +8,11 @@ * e.g: factorial is 01, add is 010, like 2!, 2+2 * precedence: determines which operators should be evaluated first * the lower the value, the higher the precedence + * hasExpression: determines if any of the operator arguments are an expression + * This way, arguments will not be solved by equation and instead + * you have to call solve on each argument yourself. + * You get the arguments as parsed arrays sigma(0, 5, 2@) becomes + * sigma(0, 5, [2, '*', '@']). See sigma operator for reference */ export default { '^': { @@ -98,5 +105,22 @@ export default { fn: Math.floor, format: '10', precedence: -1 + }, + 'sigma': { + fn(from, to, expression) { + const expr = expression.join('').replace(/,/g, ''); + const regex = new RegExp(ITERATOR_SIGN, 'g'); + + let sum = 0; + for (let i = from; i <= to; i++) { + sum += Equation.solve(expr.replace(regex, i)); + } + return sum; + }, + format: '1000', + hasExpression: true, + precedence: -1 } }; + +const ITERATOR_SIGN = '@'; diff --git a/package.json b/package.json index 95e7c6f..1ed7cee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "equations", - "version": "1.2.1", + "version": "1.3.0", "description": "", "main": "dist/index.js", "directories": { diff --git a/tests/solve.js b/tests/solve.js index 872c78f..7cc52ea 100644 --- a/tests/solve.js +++ b/tests/solve.js @@ -85,7 +85,25 @@ describe('Constats', () => { expect(M.solve('sin(PI/2)')).to.equal(1); }); - it('should work for functions as constants', () => { + it('should work for functions as constants - retry on fail', () => { expect(M.solve('RAND')).to.not.equal(M.solve('RAND')); }); }); + +describe('Sigma', () => { + it('should work with simple expressions', () => { + expect(M.solve('sigma(0, 5, @)')).to.equal(15); + }); + + it('should work with more complex expressions', () => { + expect(M.solve('sigma(0, 2, 2@+5)')).to.equal(21); + }); + + it('should work without an iterator sign', () => { + expect(M.solve('sigma(0, 2, 5*2)')).to.equal(30); + }); + + it('should work with negative start / end points', () => { + expect(M.solve('sigma(-5, -2, @)')).to.equal(-14); + }); +});