Last active
April 14, 2017 12:04
-
-
Save ylc395/214ed6a66a671c719f661998e23c56b3 to your computer and use it in GitHub Desktop.
一个解析四则运算表达式的函数
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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