JavaScript中的自定义计算函数

4ioopgfo  于 2023-02-15  发布在  Java
关注(0)|答案(2)|浏览(195)

我正在尝试在JavaScript中创建一个允许嵌套函数的自定义公式求值器。我已经能够创建基本的计算函数,如平均值,总和和最小值(以及许多其他函数),但我在嵌套函数方面遇到了麻烦,我可以使用一些帮助。
所需的行为如下所示:
用户使用语法funcName在文本输入中输入公式(arg1,arg2,...),其中funcName是计算函数的名称,arg1,arg2,...是函数的参数。参数可以是数字或其他函数(即平均(1,2)或平均(SUM(1,2),2)。我希望公式具有任意复杂的形式,只要它们以适当的语法编码。
用户按下一个按钮来计算公式。计算结果显示在屏幕上。下面是公式的另一个示例:AVERAGE(SUM(1,2),4)。此公式应按如下方式计算:
SUM(1,2)函数首先求值,返回值为3。AVERAGE(3,4)函数(也就是最终输出)接着求值,返回值为3.5。到目前为止,我已经能够创建基本的计算函数,如sum和min,但我在使用嵌套函数时遇到了麻烦。我将非常感谢任何人提供的帮助或指导。
先谢谢你!
以下是我所做的几种不同尝试之一;

function sum(...args) {
        return args.reduce((acc, arg) => {
          if (isNaN(arg)) {
            return acc;
          }
          return acc + arg;
        }, 0);
      }

      function min(...args) {
        return Math.min(...args);
      }

      function avg(...args) {
        if (args.length === 1 && typeof args[0] === 'number') {
          return args[0];
        }
        return sum(...args) / args.length;
      }

      function evaluate(formula) {
        let match = /^(\w+)\((.*)\)$/.exec(formula);
        if (!match) {
          return parseFloat(formula);
        }

        let [, func, argsStr] = match;
        let args = argsStr.split(',').map(arg => evaluate(arg.trim()));

        switch (func) {
          case 'SUM':
            return sum(...args);
          case 'MIN':
            return min(...args);
          case 'AVERAGE':
            return avg(...args);
          default:
            return NaN;
        }
      }

      let formulaInput = document.getElementById('formula');
      let evalButton = document.getElementById('eval-btn');
      let result = document.getElementById('result');

      evalButton.addEventListener('click', () => {
        let formula = formulaInput.value;
        result.innerHTML = evaluate(formula);
      });

我已经尝试了各种方法,到目前为止,我已经发现这种方法取得了一些成功,但我不认为这是最好的方法,例如,如果我不包括"if(isNaN(arg))"或其他一些变体,在控制台中我会得到:{Array(3):0:NaN 1:3 2:3}作为第一个参数,当尝试创建类似以下的复杂公式时:SUM(平均值(1,3),3),输出为NaN。

avwztpqn

avwztpqn1#

正如在评论中指出的,你需要一个合适的语言解析器,好消息是,你可以使用解析器生成器,而不是手工编写解析器,一个好的解析器生成器是PEG.js,它的语法可以是这样的:

Expression
  = head:Term tail:(_ [+-] _ Term)* {
    return tail.reduce((t, n) => [n[1], t, n[3]], head)
}

Term
  = head:Factor tail:(_ [*/] _ Factor)* {
    return tail.reduce((t, n) => [n[1], t, n[3]], head)
}

Factor
  = "(" _ expr:Expression _ ")" { return expr; }
  / Integer
  / Funcall

Funcall 
  = name:Ident '(' _ ')' { return [name] }
  / name:Ident '(' args:Args ')' { return [name, ...args] }

Args 
  = head:Expression tail:(_ ',' _ Expression)* {
    return [head, ...tail.map(t => t.pop())]
}

Integer = _ [0-9]+ { return parseInt(text(), 10); }

Ident = [A-Za-z]+ { return text() }

_ = [ \t\n\r]* { return null }

您可以在https://pegjs.org/online上试用它,然后只需单击“download parser”,您就准备好了。
此语法将输入转换为所谓的s表达式,即[operator-or-function arg1 arg2 etc]之类的嵌套数组。

FOO(1+2, BAR(3*4), 5)

将被解析为:

[
   "FOO",
   ["+", 1, 2],
   [
      "BAR",
      ["*", 3, 4]
   ],
   5
]

评估这个应该不是问题。

fjaof16o

fjaof16o2#

您可以用最嵌套函数中的值从里到外替换字符串。

function calc(s) {
    const functions = {
        SUM (...args) {
            return args.reduce((a, b) => a + b, 0);
        },
        AVERAGE (...args) {
            return args.reduce((a, b) => a + b, 0) / args.length;
        }
    }
    let t = s;
    do {
        s = t;
        t = s.replace(/([^(),]+)\(([^()]+)\)/, (_, fn, v) => fn in functions
            ? functions[fn](...v.split(',').map(Number))
            : NaN
        );
    } while (s !== t)
    return t;
} 

console.log(calc('SUM(AVERAGE(1,3),3)'));
console.log(calc('SUM(AVERAGE(1,3),3,AVERAGE(5,7))'));

相关问题