move library files to lib
This commit is contained in:
11
lib/constats.js
Normal file
11
lib/constats.js
Normal file
@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Constats
|
||||
* Keys must be UPPERCASE
|
||||
* Values Can be a constant value or a function returning a value
|
||||
* this function doesn't take any arguments (use case: random constats)
|
||||
*/
|
||||
export default {
|
||||
'PI': Math.PI,
|
||||
'E': Math.E,
|
||||
'RAND': Math.random
|
||||
};
|
78
lib/helpers.js
Normal file
78
lib/helpers.js
Normal file
@ -0,0 +1,78 @@
|
||||
export const parseFormat = function(a) {
|
||||
const split = a.split('1');
|
||||
return {
|
||||
left: split[0].length,
|
||||
right: split[1].length
|
||||
};
|
||||
};
|
||||
|
||||
export const isNumber = a => {
|
||||
return !isNaN(+a);
|
||||
};
|
||||
|
||||
export const parseNumbers = (a) => {
|
||||
return a.map(b => {
|
||||
if (isNumber(b)) {
|
||||
return parseFloat(b);
|
||||
}
|
||||
if (Array.isArray(b)) {
|
||||
return parseNumbers(b);
|
||||
}
|
||||
return b;
|
||||
});
|
||||
};
|
||||
|
||||
export const dive = (arr, n) => {
|
||||
let result = arr;
|
||||
for (let i = 0; i < n; ++i) {
|
||||
result = result[result.length - 1];
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
export const deep = (arr, n, index = 0) => {
|
||||
if (n < 2) {
|
||||
return {arr, index};
|
||||
}
|
||||
|
||||
let d = arr.reduce((a, b, i) => {
|
||||
if (Array.isArray(b)) {
|
||||
let {arr, index: x} = deep(b, n - 1, i),
|
||||
merged = a.concat(arr);
|
||||
|
||||
index = x;
|
||||
return merged;
|
||||
}
|
||||
return a;
|
||||
}, []);
|
||||
|
||||
return {arr: d, index};
|
||||
};
|
||||
|
||||
export const diveTo = (arr, indexes, replace) => {
|
||||
let answer = [];
|
||||
if (indexes.some(Array.isArray)) {
|
||||
for (let index of indexes) {
|
||||
answer.push(diveTo(arr, index, replace));
|
||||
}
|
||||
} else {
|
||||
arr[indexes[0]] = replace;
|
||||
return replace;
|
||||
}
|
||||
|
||||
return answer;
|
||||
};
|
||||
|
||||
export const flatten = (arr) => {
|
||||
if (!Array.isArray(arr) || !arr.some(Array.isArray)) {
|
||||
return arr;
|
||||
}
|
||||
|
||||
return arr.reduce((a, b) => {
|
||||
return a.concat(flatten(b));
|
||||
}, []);
|
||||
};
|
||||
|
||||
export const removeSymbols = string => {
|
||||
return string.replace(/\W/g, '');
|
||||
};
|
296
lib/index.js
Normal file
296
lib/index.js
Normal file
@ -0,0 +1,296 @@
|
||||
import ReadStream from './readstream';
|
||||
import operators from './operators';
|
||||
import constants from './constats';
|
||||
import * as _ from './helpers';
|
||||
|
||||
let Mathstring = {
|
||||
/**
|
||||
* Solves the given math expression, following these steps:
|
||||
* 1. Replace constants in the expression
|
||||
* 2. parse the expression, separating numbers and operators into
|
||||
* an array
|
||||
* 3. Sort the stack by operators' precedence, it actually groups the
|
||||
* operator with it's arguments n-level deep
|
||||
* 4. Apply parseFloat to numbers of the array
|
||||
* 5. Solve groups recursively
|
||||
*
|
||||
* @param {String} expression
|
||||
* The math expression to solve
|
||||
* @return {Number}
|
||||
* Result of the expression
|
||||
*/
|
||||
solve(expression) {
|
||||
// replace constants with their values
|
||||
expression = replaceConstants(expression);
|
||||
|
||||
let stack = parseExpression(expression);
|
||||
stack = sortStack(stack);
|
||||
stack = _.parseNumbers(stack);
|
||||
stack = solveStack(stack);
|
||||
|
||||
return stack;
|
||||
},
|
||||
/**
|
||||
* Creates an equation function which replaces variables
|
||||
* in the given expression with the values specified in order,
|
||||
* and solves the new expression
|
||||
*
|
||||
* Example:
|
||||
* equation('x+y+z')(2, 5, 6) => solve('2+5+6')
|
||||
*
|
||||
* @param {String} expression
|
||||
* The expression to create an equation for (containing variables)
|
||||
* @return {Function}
|
||||
* The function which replaces variables with values in order
|
||||
* and solves the expression
|
||||
*/
|
||||
equation(expression) {
|
||||
let stack = parseExpression(expression);
|
||||
let variables = [];
|
||||
|
||||
stack.forEach(a => {
|
||||
if (!_.isNumber(a) && !operators[a] && a === a.toLowerCase()) {
|
||||
// grouped variables like (y) need to have their parantheses removed
|
||||
variables.push(_.removeSymbols(a));
|
||||
}
|
||||
});
|
||||
|
||||
return function(...args) {
|
||||
expression = expression.replace(/[a-z]*/g, a => {
|
||||
let index = variables.indexOf(a);
|
||||
if (index > -1) {
|
||||
return args[index] || 0;
|
||||
}
|
||||
return a;
|
||||
});
|
||||
|
||||
console.log(variables, expression);
|
||||
return Mathstring.solve(expression);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const solveStack = stack => {
|
||||
// $0(stack);
|
||||
if (stack.some(Array.isArray)) {
|
||||
stack = stack.map(group => {
|
||||
if (!Array.isArray(group)) {
|
||||
return group;
|
||||
}
|
||||
return solveStack(group);
|
||||
});
|
||||
|
||||
return solveStack(stack);
|
||||
} else {
|
||||
return evaluate(stack);
|
||||
}
|
||||
};
|
||||
|
||||
const PRECEDENCES = Object.keys(operators).map(key => {
|
||||
return operators[key].precedence;
|
||||
});
|
||||
|
||||
const MAX_PRECEDENCE = Math.max(...PRECEDENCES);
|
||||
const MIN_PRECEDENCE = Math.min(...PRECEDENCES);
|
||||
|
||||
/**
|
||||
* Parses the given expression into an array of separated
|
||||
* numbers and operators/functions.
|
||||
* The result is passed to parseGroups
|
||||
*
|
||||
* @param {String} expression
|
||||
* The expression to parse
|
||||
* @return {Array}
|
||||
* The parsed array
|
||||
*/
|
||||
const parseExpression = expression => {
|
||||
let stream = new ReadStream(expression),
|
||||
stack = [],
|
||||
record = '';
|
||||
|
||||
// Create an array of separated numbers & operators
|
||||
while (stream.next()) {
|
||||
const cur = stream.current();
|
||||
if (cur === ' ') {
|
||||
continue;
|
||||
}
|
||||
// it's probably a function with a length more than one
|
||||
if (!_.isNumber(cur) && !operators[cur] && cur !== '.') {
|
||||
record += cur;
|
||||
} else if (record.length) {
|
||||
stack.push(record, cur);
|
||||
record = '';
|
||||
} else if (_.isNumber(stack[stack.length - 1]) &&
|
||||
(_.isNumber(cur) || cur === '.')) {
|
||||
|
||||
stack[stack.length - 1] += cur;
|
||||
} else {
|
||||
stack.push(cur);
|
||||
}
|
||||
}
|
||||
if (record.length) {
|
||||
stack.push(record);
|
||||
}
|
||||
|
||||
// $0(stack);
|
||||
return parseGroups(stack);
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes the parsed array from parseExpression and
|
||||
* groups up expressions in parantheses in deep arrays
|
||||
*
|
||||
* Example: 2+(5+4) becomes [2, [5, '+', 4]]
|
||||
*
|
||||
* @param {Array} stack
|
||||
* The parsed expression
|
||||
* @return {Array}
|
||||
* Grouped up expression
|
||||
*/
|
||||
const parseGroups = stack => {
|
||||
// Parantheses become inner arrays which will then be processed first
|
||||
let sub = 0;
|
||||
return stack.reduce((a, b) => {
|
||||
if (b[b.length - 1] === '(') {
|
||||
if (b.length > 1) {
|
||||
_.dive(a, sub).push(b.slice(0, -1), []);
|
||||
} else {
|
||||
_.dive(a, sub).push([]);
|
||||
}
|
||||
sub++;
|
||||
} else if (b === ')') {
|
||||
sub--;
|
||||
} else {
|
||||
_.dive(a, sub).push(b);
|
||||
}
|
||||
return a;
|
||||
}, []);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gives information about an operator's format
|
||||
* including number of left and right arguments
|
||||
*
|
||||
* @param {String/Object} operator
|
||||
* The operator object or operator name (e.g. +, -)
|
||||
* @return {Object}
|
||||
* An object including the count of left and right arguments
|
||||
*/
|
||||
const formatInfo = operator => {
|
||||
let op = typeof operator === 'string' ? operators[operator]
|
||||
: operator;
|
||||
|
||||
if (!op) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const format = op.format.split('1'),
|
||||
left = format[0].length,
|
||||
right = format[1].length;
|
||||
|
||||
return { left, right };
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Groups up operators and their arguments based on their precedence
|
||||
* in deep arrays, the higher the priority, the deeper the group.
|
||||
* This simplifies the evaluating process, the only thing to do is to
|
||||
* evaluate from bottom up, evaluating deep groups first
|
||||
*
|
||||
* @param {Array} stack
|
||||
* The parsed and grouped expression
|
||||
* @return {Array}
|
||||
* Grouped expression based on precedences
|
||||
*/
|
||||
const sortStack = stack => {
|
||||
for (let [index, item] of stack.entries()) {
|
||||
if (Array.isArray(item)) {
|
||||
stack.splice(index, 1, sortStack(item));
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = MIN_PRECEDENCE; i <= MAX_PRECEDENCE; i++) {
|
||||
for (let index = 0; index < stack.length; ++index) {
|
||||
let item = stack[index];
|
||||
let op = operators[item];
|
||||
|
||||
if (!op || op.precedence !== i) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const { left, right } = formatInfo(op);
|
||||
let group = stack.splice(index - left, left + right + 1, []);
|
||||
stack[index - left] = group;
|
||||
|
||||
for (let y = 0; y < i; y++) {
|
||||
group = [group];
|
||||
}
|
||||
|
||||
index -= right;
|
||||
}
|
||||
}
|
||||
|
||||
return stack;
|
||||
};
|
||||
|
||||
/**
|
||||
* Evaluates the given math expression.
|
||||
* The expression is an array with an operator and arguments
|
||||
*
|
||||
* Example: evaluate([2, '+', 4]) == 6
|
||||
*
|
||||
* @param {Array} stack
|
||||
* A single math expression
|
||||
* @return {Number}
|
||||
* Result of the expression
|
||||
*/
|
||||
const evaluate = stack => {
|
||||
const op = findOperator(stack);
|
||||
if (!op) {
|
||||
return stack[0];
|
||||
}
|
||||
const { left } = formatInfo(op);
|
||||
|
||||
let leftArguments = stack.slice(0, left),
|
||||
rightArguments = stack.slice(left + 1);
|
||||
|
||||
return operators[op].fn(...leftArguments, ...rightArguments);
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds the first operator in an array and returns it
|
||||
*
|
||||
* @param {Array} arr
|
||||
* The array to look for an operator in
|
||||
* @return {Object}
|
||||
* The operator object or null if no operator is found
|
||||
*/
|
||||
const findOperator = arr => {
|
||||
for (let o of arr) {
|
||||
if (typeof o === 'string') {
|
||||
return o;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Replaces constants in a string with their values
|
||||
*
|
||||
* @param {String} expression
|
||||
* The expression to replace constants in
|
||||
* @return {String}
|
||||
* The expression with constants replaced
|
||||
*/
|
||||
const replaceConstants = expression => {
|
||||
return expression.replace(/[A-Z]*/g, (a) => {
|
||||
let c = constants[a];
|
||||
if (!c) {
|
||||
return a;
|
||||
}
|
||||
return typeof c === 'function' ? c() : c;
|
||||
});
|
||||
};
|
||||
|
||||
export default Mathstring;
|
92
lib/operators.js
Normal file
92
lib/operators.js
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Operators and Functions
|
||||
* fn: function used to evaluate value
|
||||
* format: the format using which arguments are parsed:
|
||||
* 0 indicates an argument and 1 indicates the operator
|
||||
* 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
|
||||
*/
|
||||
export default {
|
||||
'^': {
|
||||
fn: (a, b) => Math.pow(a, b),
|
||||
format: '010',
|
||||
precedence: 0
|
||||
},
|
||||
'*': {
|
||||
fn: (a, b) => a * b,
|
||||
format: '010',
|
||||
precedence: 1
|
||||
},
|
||||
'/': {
|
||||
fn: (a, b) => a / b,
|
||||
format: '010',
|
||||
precedence: 1
|
||||
},
|
||||
'%': {
|
||||
fn: (a, b) => a % b,
|
||||
format: '010',
|
||||
precedence: 1
|
||||
},
|
||||
'\\': {
|
||||
fn: (a, b) => Math.floor(a / b),
|
||||
format: '010',
|
||||
precedence: 1
|
||||
},
|
||||
'+': {
|
||||
fn: (a, b) => a + b,
|
||||
format: '010',
|
||||
precedence: 2
|
||||
},
|
||||
'-': {
|
||||
fn: (a, b) => a - b,
|
||||
format: '010',
|
||||
precedence: 2
|
||||
},
|
||||
'!': {
|
||||
fn: (a) => {
|
||||
let sum = 1;
|
||||
for (var i = 0; i < a; ++i) {
|
||||
sum *= i;
|
||||
}
|
||||
return sum;
|
||||
},
|
||||
format: '01',
|
||||
precedence: 2
|
||||
},
|
||||
'log': {
|
||||
fn: Math.log,
|
||||
format: '10',
|
||||
precedence: -1
|
||||
},
|
||||
'ln': {
|
||||
fn: Math.log,
|
||||
format: '10',
|
||||
precedence: -1
|
||||
},
|
||||
'lg': {
|
||||
fn: (a) => 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
|
||||
}
|
||||
};
|
43
lib/readstream.js
Normal file
43
lib/readstream.js
Normal file
@ -0,0 +1,43 @@
|
||||
export default function(string) {
|
||||
let i = 0, buffer = [];
|
||||
return {
|
||||
next() {
|
||||
buffer.push(string[i]);
|
||||
|
||||
if (i >= string.length) {
|
||||
return null;
|
||||
}
|
||||
return string[i++];
|
||||
},
|
||||
current() {
|
||||
return string[i - 1];
|
||||
},
|
||||
index() {
|
||||
return i - 1;
|
||||
},
|
||||
to(n) {
|
||||
let temp = '';
|
||||
const dest = i + n;
|
||||
for (i = i; i < dest; ++i) {
|
||||
temp += [string[i]];
|
||||
}
|
||||
return temp;
|
||||
},
|
||||
drain() {
|
||||
return buffer.splice(0, buffer.length);
|
||||
},
|
||||
replace(start, end, replace) {
|
||||
let temp = string.split('');
|
||||
temp.splice(start, end, replace);
|
||||
string = temp.join('');
|
||||
|
||||
i = i - (end - start);
|
||||
},
|
||||
go(n) {
|
||||
i += n;
|
||||
},
|
||||
all() {
|
||||
return string;
|
||||
}
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user