接上一篇文章。算法:如何实现一个计算器
优化点
- 支持负号
- - 子表达式使用栈结构提速
支持负号-
示例
-100+2010*(-2*-5)
分析
若匹配到token为减号,则判断其前面一个token是否是符号(+*/等,不能包括右括号),若为符号则认为是负号。
若减号出现在第一位,则也是负号。
修改代码input2Tokens()
function input2Tokens(input) {
const pattern = /[\+\-\*\/\(\)]/g;
const tokens = [];
let matches;
// 每次匹配开始的位置
let lastIndex = 0;
// 匹配符号
// exec每次会从上次匹配的位置往后去匹配
while ((matches = pattern.exec(input))) {
const { index } = matches;
// 符号位前面有数字 将从开始位到符号位之间所有字符作为一个token
if (lastIndex < index) {
tokens.push(input.slice(lastIndex, index));
}
// 符号位
const isMinusSign =
matches[0] === '-' &&
(index === 0 || /[\+\-\*\/\(]/.test(tokens[tokens.length - 1]));
if (isMinusSign) continue;
tokens.push(matches[0]);
lastIndex = index + 1;
}
// 最后一个符号位到结尾的全部数字作为一个token
if (lastIndex !== input.length) {
tokens.push(input.slice(lastIndex));
}
return tokens;
}
子表达式使用栈结构处理
- 准备测试案例
-100/((3+2)*4)-(-7+13)*3 - 处理成tokens后:
tokens = ["-100","/","(","(","3","+","2",")","*","4",")","-","(","-7","+","13",")","*","3"]
- 将tokens一维数组处理成多维的结构
tokens = [
'-100',
'/',
[['3', '+', '2'], '*', '4'],
'-',
['-7', '+', '13'],
'*',
'3',
];
- 计算tokens
function calculate(tokens) {
for (let i = 0; i < tokens.length; i++) {
if (Array.isArray(tokens[i])) {
tokens[i] = calculate(tokens[i]);
}
}
// 计算一维数组,没有字表达式
return _calculate(tokens);
}
function _calculate(tokens) {
const signIndex = getHighestPrioritySignIndex(tokens);
const result = calcTokens(getCalcTokens(tokens, signIndex));
if (tokens.length === 0) return result;
// 将结果放回tokens
tokens.splice(signIndex - 1, 0, result);
return _calculate(tokens);
}
5.使用栈结构处理tokens为多维结构(难点)
function transformTokens(tokens) {
let current = [];
const stack = [current];
let tmp;
for (let i = 0; i < tokens.length; i++) {
const token = tokens[i];
if (token === '(') {
tmp = [];
current.push(tmp);
current = tmp;
stack.push(tmp);
} else if (token === ')') {
stack.pop();
current = stack[stack.length - 1];
} else {
current.push(token);
}
}
return current;
}
完整代码
// 将输入字符串转成tokens
function input2Tokens(input) {
const pattern = /[\+\-\*\/\(\)]/g;
const tokens = [];
let matches;
// 每次匹配开始的位置
let lastIndex = 0;
// 匹配符号
// exec每次会从上次匹配的位置往后去匹配
while ((matches = pattern.exec(input))) {
const { index } = matches;
// 符号位前面有数字 将从开始位到符号位之间所有字符作为一个token
if (lastIndex < index) {
tokens.push(input.slice(lastIndex, index));
}
// 符号位
const isMinusSign =
matches[0] === '-' &&
(index === 0 || /[\+\-\*\/\(]/.test(tokens[tokens.length - 1]));
if (isMinusSign) continue;
tokens.push(matches[0]);
lastIndex = index + 1;
}
// 最后一个符号位到结尾的全部数字作为一个token
tokens.push(input.slice(lastIndex));
return tokens;
}
// 获取优先级最高的符号的位置
function getHighestPrioritySignIndex(tokens) {
// 乘除法优先级更高
let index = tokens.findIndex(token => ['*', '/'].includes(token));
if (index === -1) {
index = tokens.findIndex(token => ['+', '-'].includes(token));
}
return index;
}
// 取出符号和两端的数字
function getCalcTokens(tokens, signIndex) {
return tokens.splice(signIndex - 1, 3);
}
// 计算符号及两端数字
function calcTokens(tokens) {
let [left, sign, right] = tokens;
left = Number(left);
right = Number(right);
if (sign === '+') return left + right;
if (sign === '-') return left - right;
if (sign === '*') return left * right;
if (sign === '/') return left / right;
}
function transformTokens(tokens) {
let current = [];
const stack = [current];
for (let i = 0; i < tokens.length; i++) {
const token = tokens[i];
if (token === '(') {
tmp = [];
current.push(tmp);
current = tmp;
stack.push(tmp);
} else if (token === ')') {
stack.pop();
current = stack[stack.length - 1];
} else {
current.push(token);
}
}
return current;
}
// 重点
function calculate(input) {
function calcOneDimension(tokens) {
const signIndex = getHighestPrioritySignIndex(tokens);
const result = calcTokens(getCalcTokens(tokens, signIndex));
if (tokens.length === 0) return result;
// 将结果放回tokens
tokens.splice(signIndex - 1, 0, result);
return calcOneDimension(tokens);
}
// 处理多维的token
function calcMultiDimension(tokens) {
for (let i = 0; i < tokens.length; i++) {
if (Array.isArray(tokens[i])) {
tokens[i] = calcMultiDimension(tokens[i]);
}
}
return calcOneDimension(tokens);
}
let tokens = input2Tokens(input);
// 转成多维数组
tokens = transformTokens(tokens);
return calcMultiDimension(tokens);
}