Skip to content

Instantly share code, notes, and snippets.

@ylc395
Last active April 14, 2017 12:04
Show Gist options
  • Save ylc395/214ed6a66a671c719f661998e23c56b3 to your computer and use it in GitHub Desktop.
Save ylc395/214ed6a66a671c719f661998e23c56b3 to your computer and use it in GitHub Desktop.
一个解析四则运算表达式的函数
(function(expression) {
let operators = {
'+' : (a, b) => a + b,
'-': (a, b) => a - b,
'*': (a, b) => a * b,
'/': (a, b) => {
if(b === 0) throw Error('除数不能为0');
return a / b;
}
};
let isNumber = token => typeof token === 'number' || /^-?\d+(\.\d+)?$/.test(token) ;
let isOperator = Array.prototype.includes.bind(['(', ')', ...Object.keys(operators)]);
let isPrior = (operator1, operator2) => ['*', '/'].includes(operator1) && ['+', '-'].includes(operator2);
let isEqual = (...operators) => operators.every(o => ['-', '+'].includes(o)) || operators.every(o => ['*', '/'].includes(o));
class TreeNode {
constructor(value = null) {
if(value instanceof TreeNode) return value;
this.value = value
this.left = this.right = null;
}
noChild() {
return this.left === null && this.right === null;
}
setLeft(value) {
this.left = new TreeNode(value);
this.left.parent = this;
}
setRight(value) {
this.right = new TreeNode(value);
this.right.parent = this;
}
}
// 将字符串分解为token数组
function getTokens(expression) {
let array = [];
let token = '';
for(let i = 0; i < expression.length; i++) {
let char = expression[i];
if(char === ' ') continue;
if(isNumber(char) || char === '.') {
token += char;
}
if(char === '-' && expression[i - 1] === '(') {
token += char;
continue;
}
if(isOperator(char)) {
if(token) array.push(token);
array.push(char);
token = '';
}
}
token && array.push(token);
return array;
}
function parseTokens(tokens) {
let stack = [];
let node;
let isOperator = token => token in operators;
// 根据tokens列表求值。tokens内不含括号
function parseSimpleTokens(tokens) {
let lastOperatorNode;
let root;
if(tokens.length === 1 && isNumber(tokens[0])) return Number(tokens[0]);
// 构建表达式树
for(let i = 0; i < tokens.length; i++) {
let token = tokens[i];
if (!isOperator(token)) continue;
if(!isNumber(tokens[i + 1])) throw Error(`${token}后出现了${tokens[i + 1]}`);
let node = new TreeNode(token);
node.setRight(tokens[i + 1]);
if(lastOperatorNode) {
if(isPrior(token, lastOperatorNode.value)) {
node.setLeft(lastOperatorNode.right);
lastOperatorNode.setRight(node);
} else {
let targetNode = lastOperatorNode; // 上一个优先级相同的节点
while(true) {
if(isEqual(token, targetNode.value)) break;
if(!targetNode.parent) break;
targetNode = targetNode.parent;
}
if(targetNode.parent) {
targetNode.parent.setRight(node);
} else {
root = node;
}
node.setLeft(targetNode);
}
} else {
node.setLeft(tokens[i - 1]);
root = node;
}
lastOperatorNode = node;
}
// 根据表达式树得出值(后序遍历)
function infixorder(root) {
if(root.noChild()) return Number(root.value);
let operator = root.value;
let num1 = infixorder(root.left);
let num2 = infixorder(root.right);
return operators[operator](num1, num2);
}
return infixorder(root);
};
// 解析tokens
for(let i = 0; i < tokens.length; i++) {
let token = tokens[i];
if(token === '(') stack.push(i)
if(token === ')') {
let start = stack.pop();
let end = i;
let value = parseSimpleTokens(tokens.slice(start + 1, end));
tokens.splice(start, end - start + 1, value);
i = start;
}
}
return parseSimpleTokens(tokens);
}
window.calc = function(str) {
return parseTokens(getTokens(str));
}
})();
(function(){
var assert = function (str, handler) {
var res = handler(str);
if (eval(str) === res) {
console.log('OK', str);
return true;
}else{
console.error('ERROR', str, res);
return false;
}
}
var cases = [
'1 + 2',
'1.1+ 3.45',
'2*3-6',
'4-6*5',
'(1 + 2) * 3',
'4 - (5 - 2 - ( 8 - 3))',
'3+4*219.3/(9+3)-3-43+21*3-5/6+(4+5.2*8-6*(90-(2+3)*(-40-6-(2-8.7)))/3)'
];
window.run = function(handler){
var result = [0, 0];
var startTime = Date.now();
console.group('running');
cases.forEach(function(val){
var r = false;
try{
r = assert(val, handler);
}catch(e){};
result[r ? 0 : 1]++;
});
var duration = Date.now() - startTime;
console.groupEnd('running');
console.group('finish');
console.log('DURATION:', duration);
console.log('PASS:', result[0]);
console.error('ERROR:', result[1]);
console.groupEnd('finish');
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment