JavaScript - 编写一个可以解决数学表达式的函数(没有eval)

问题描述 投票:2回答:2

最终我想接受这个:

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)如何更有效地分割这些术语,而不为每个术语创建一个新变量,并手动递归映射每个术语?看起来我应该只有一些数组字典跟踪操作顺序(现在不考虑括号或指数):["*","/","+","-"] - 并且给定该数组,生成类似于上例中的最后一个数组(“dividers”)它只包含常量,并以某种方式跟踪每个存储的数组遵循的元素...

2)如何给出值数组的表达式?

我只是对逻辑有点困惑,我想我需要从最后一个数组开始处理并一次解决一个常量,跟踪哪个运算符是当前运算符,但我不确定究竟是什么。

javascript arrays math
2个回答
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

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"));

以上是非常简单查询的解决方案,只有+, - ,*,/(没有括号,例如)。对于像你的第一个例子那样求解方程式需要更多的工作。

编辑:添加一个解析函数来返回树。

© www.soinside.com 2019 - 2024. All rights reserved.