JavaScript-写一个可以解析数学表达式的函数(无eval)

时间:2019-04-29 04:02:14

标签: javascript arrays math

最终我要接受这个:

2x + 3 = 5

求解x,方法是先从两边减去3,使2x = 2,然后将两边除以2,从而x = 1。我在想很多事情,应该如何在JavaScript中创建这样的函数,该函数可以返回按顺序完成的一系列步骤,包括结果。显然,“ eval”对此无能为力,因此貌似必须重新创建方程式。

我最初想到首先忽略X,然后尝试制作一个可以求解简单方程式的函数,而无需eval或任何内置函数。

我认为第一步是使用.split分解术语,但是我遇到了一些麻烦,因为我需要拆分多个 个符号。例如,假设我有一个简单的表达式求值:3 - 6 * 3 / 9 + 5。因此,在进入操作顺序之前,仅分解每个术语(并将其分类)是困难的部分,这是我目前主要的具体问题

我开始只是一个接一个地拆分,但是我遇到了一些问题,尤其是考虑顺序。

function solve(eq) {
    var minuses = eq.split("-"),
            pluses = minuses.map(x=> x.split("+")),
            timeses = pluses.map(x=>x.map(y=>y.split("*"))),
      dividers = timeses.map(x=>x.map(y=>y.map(z=>z.split("/"))));
            console.log(minuses, pluses, timeses, dividers);
}

solve("3 - 6 * 3 / 9 + 5");

如您所见,对于每个连续的运算符,我都需要遍历前一个运算符的每个元素以进行拆分,然后剩下一个数组数组等。

所以1)如何在不为每个变量创建新变量的情况下更有效地分解这些术语,并手动递归地映射每个变量?看来我应该只拥有某种数组字典来跟踪操作顺序(现在不考虑括号或指数):["*","/","+","-"]-并给出该数组,生成类似于上例中最后一个数组的内容( “除法器”)仅包含常量,并以某种方式跟踪每个存储数组遵循的元素...

和2)在给定值数组的情况下如何求解表达式?

我只是对逻辑有点困惑,我想我需要从最后一个数组开始处理,一次求解一个常量,跟踪哪个运算符是当前的,但是我不确定完全是

2 个答案:

答案 0 :(得分:2)

虽然您的问题不需要构造,binary expression tree是集思广益解决数学查询逻辑的好方法。

因此对于查询3 - 6 * 3 / 9 + 5,代表性的二进制表达式树为:

plus
  |_minus
  | |_3
  | |_divide
  |   |_times
  |   | |_3
  |   | |_6
  |   |_9
  |_5

要求解上面的树,您需要从叶级到根递归求解。

同样,您不需要构造树。它只是帮助我们了解在此处进行解析的逻辑:

  • 获取查询中的最后一个减号或加号表达式,并求解该表达式的左右子级。
  • 如果没有加/减,则获取最后一次/除法表达式并求解左右孩子
  • 如果遇到数字,请返回该数字值。

考虑到以上逻辑,这是一个实现:

function solve(str) {
  var expressionIndex = Math.max(str.lastIndexOf("-"), str.lastIndexOf("+"));
  if (expressionIndex === -1) {
    expressionIndex = Math.max(str.lastIndexOf("*"), str.lastIndexOf("/"));
  }
  if (expressionIndex === -1) {
    var num = Number.parseInt(str.trim());
    if (isNaN(num)) {
      throw Exception("not a valid number");
    } else {
      return num;
    }
  } else {
    var leftVal = solve(str.substring(0, expressionIndex).trim());
    var rightVal = solve(str.substring(expressionIndex + 1).trim());
    switch (str[expressionIndex]) {
      case "+":
        return leftVal + rightVal;
      case "-":
        return leftVal - rightVal;
      case "*":
        return leftVal * rightVal;
      case "/":
        return leftVal / rightVal;
    }
  }
}

function parse(str) {
  var expressionIndex = Math.max(str.lastIndexOf("-"), str.lastIndexOf("+"));
  if (expressionIndex === -1) {
    expressionIndex = Math.max(str.lastIndexOf("*"), str.lastIndexOf("/"));
  }
  if (expressionIndex === -1) {
    var num = Number.parseInt(str.trim());
    if (isNaN(num)) {
      throw Exception("not a valid number");
    } else {
      return { type: "number", value: num };
    }
  } else {
    var leftNode = parse(str.substring(0, expressionIndex).trim());
    var rightNode = parse(str.substring(expressionIndex + 1).trim());
    return {
      type: "expression",
      value: str[expressionIndex],
      left: leftNode,
      right: rightNode
    };
  }
}

console.log(solve("3 - 6 * 3 / 9 + 5"));
console.log(parse("3 - 6 * 3 / 9 + 5"));

以上是仅使用+,-,*,/(例如不带括号)的非常简单查询的解决方案。为了像第一个示例一样求解方程,需要进行大量工作。

编辑:添加解析函数以返回树。

答案 1 :(得分:2)

您可以按照以下步骤进行操作:

  • 首先使用split()并除以+-,这将在乘法和除法之后发生。
  • 然后在数组上使用map(),然后split()*再次使用/
  • 现在我们有一个函数,它将对带有运算符的数字数组求值为单个数字。
  • 传递嵌套数组以完成乘法和除法。
  • 然后将结果再次传递给sovleSingle并执行加法和减法。

只要没有括号(),该功能与eval相同。

注意:这与在+-中先出现或在*/中先出现无关紧要。但是*,/应该在+,-

之前出现

function solveSingle(arr){
  arr = arr.slice();
  while(arr.length-1){
    if(arr[1] === '*') arr[0] = arr[0] * arr[2]
    if(arr[1] === '-') arr[0] = arr[0] - arr[2]
    if(arr[1] === '+') arr[0] = +arr[0] + (+arr[2])
    if(arr[1] === '/') arr[0] = arr[0] / arr[2]
    arr.splice(1,1);
    arr.splice(1,1);
  }
  return arr[0];
}

function solve(eq) {
  let res = eq.split(/(\+|-)/g).map(x => x.trim().split(/(\*|\/)/g).map(a => a.trim()));
  res = res.map(x => solveSingle(x)); //evaluating nested * and  / operations.
   
  return solveSingle(res) //at last evaluating + and -
  
  
}

console.log(solve("3 - 6 * 3 / 9 + 5")); //6
console.log(eval("3 - 6 * 3 / 9 + 5")) //6