对计算器算法的优化

150 阅读3分钟

接上一篇文章。算法:如何实现一个计算器

优化点

  1. 支持负号-
  2. 子表达式使用栈结构提速

支持负号-

示例

  1. -100+20
  2. 10*(-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;
}

image.png

子表达式使用栈结构处理

  1. 准备测试案例-100/((3+2)*4)-(-7+13)*3
  2. 处理成tokens后:
tokens = ["-100","/","(","(","3","+","2",")","*","4",")","-","(","-7","+","13",")","*","3"]
  1. 将tokens一维数组处理成多维的结构
tokens = [
  '-100',
  '/',
  [['3', '+', '2'], '*', '4'],
  '-',
  ['-7', '+', '13'],
  '*',
  '3',
];
  1. 计算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);
}