如何将输入字符串转成表达式运行

129 阅读3分钟

第一种 使用eval()函数

let res = eval("2+(2*2+(1+3)*5-2*1)-1") // 23

第二种 使用new Function

function safeEval(source) {
  // 使用 Function 构造函数创建一个新的函数
  const fn = new Function("return " + source);
  try {
    // 执行这个函数并返回结果
    return fn();
  } catch (e) {
    // 如果有错误,抛出错误
    throw new Error(`Error evaluating expression: ${source}\n${e}`);
  }
}

// 测试
console.log(safeEval("1 + 2")); // 输出:3
console.log(safeEval('"Hello, " + "world!"')); // 输出:"Hello, 

第三种 Shunting Yard 算法

Shunting Yard 算法是一种将中缀表达式(人类常用的数学表达式,如 3 + 4 * 2)转换为后缀表达式(也称为逆波兰表示法,如 3 4 2 * +)的算法。后缀表达式的优势在于它可以非常简单和高效地被计算机计算,因为它不需要处理操作符优先级或括号,所有的操作都是从左到右进行的。

Shunting Yard 算法由 Edsger Dijkstra 在 1960 年代提出,它的名字来源于铁路编组站(shunting yard),因为在编组站中火车车厢会根据目的地被重新排序,类似于算法中操作数和操作符的重新排列。

下面是 Shunting Yard 算法的基本步骤:

  1. 初始化

    • 创建一个空的操作数栈(通常用于存储数字或其他操作数)。
    • 创建一个空的操作符栈(用于临时存储操作符,包括括号)。
  2. 处理输入

    • 读取输入表达式的每个字符(或标记)。

    • 对于每个字符:

      • 如果是操作数,将其压入操作数栈。

      • 如果是左括号 '(',将其压入操作符栈。

      • 如果是右括号 ')',则:

        • 反复从操作符栈弹出并压入操作数栈直到遇到左括号 '('
        • 弹出左括号,但不压入操作数栈(仅用于匹配)。
      • 如果是操作符(如 +-*/):

        • 反复从操作符栈弹出并压入操作数栈所有优先级高于或等于当前操作符的操作符。
        • 将当前操作符压入操作符栈。
  3. 处理剩余部分

    • 当输入结束时,将操作符栈中剩余的所有操作符依次弹出并压入操作数栈。
  4. 生成后缀表达式

    • 操作数栈现在包含了后缀表达式的元素,按照栈中元素的顺序读出即可。

为了计算后缀表达式,可以使用一个单独的栈。对于后缀表达式中的每个元素:

  • 如果是操作数,将其压入栈。
  • 如果是操作符,从栈中弹出相应数量的操作数,应用操作符,然后将结果压回栈中。
  • 最终栈顶的元素就是表达式的结果。

以下是一个使用 Shunting Yard 算法将中缀表达式转换为后缀表达式的示例:

中缀表达式:3 + 4 * 2 / ( 1 - 5 ) ^ 2 ^ 3

后缀表达式:3 4 2 * 1 5 - 2 3 ^ ^ / +

通过使用 Shunting Yard 算法,我们可以在不考虑操作符优先级的情况下,有效地解析和计算复杂的数学表达式。

function calculateExpression(expression) {
  const tokens = expression.match(/\d+|\+|-|\*|\/|\(|\)/g);
  return calculateShuntingYard(tokens);
}

function calculateShuntingYard(tokens) {
  const operatorStack = [];
  const operandStack = [];
  const precedence = { "+": 1, "-": 1, "*": 2, "/": 2 };

  for (let i = 0; i < tokens.length; i++) {
    const token = tokens[i];
    if (!isNaN(token)) {
      operandStack.push(parseInt(token));
    } else if (token === "(") {
      operatorStack.push(token);
    } else if (token === ")") {
      while (
        operatorStack.length &&
        operatorStack[operatorStack.length - 1] !== "("
      ) {
        applyOperator(operandStack, operatorStack.pop());
      }
      operatorStack.pop();
    } else {
      while (
        operatorStack.length &&
        precedence[token] <= precedence[operatorStack[operatorStack.length - 1]]
      ) {
        applyOperator(operandStack, operatorStack.pop());
      }
      operatorStack.push(token);
    }
  }

  while (operatorStack.length) {
    applyOperator(operandStack, operatorStack.pop());
  }

  return operandStack[0];
}

function applyOperator(operandStack, operator) {
  const right = operandStack.pop();
  const left = operandStack.pop();
  switch (operator) {
    case "+":
      operandStack.push(left + right);
      break;
    case "-":
      operandStack.push(left - right);
      break;
    case "*":
      operandStack.push(left * right);
      break;
    case "/":
      operandStack.push(left / right);
      break;
  }
}

const expression = "2+(2*2+(1+3)*5-2*1)-1";
const res = calculateExpression(expression);
console.log(res); // 应输出23