【问题标题】:How can I modify my Shunting-Yard Algorithm so it accepts unary operators?如何修改我的调车场算法以使其接受一元运算符?
【发布时间】:2009-10-20 08:00:16
【问题描述】:

我一直致力于在 JavaScript 中为类实现 Shunting-Yard 算法。

这是我目前的工作:

var userInput = prompt("Enter in a mathematical expression:");
var postFix = InfixToPostfix(userInput);
var result = EvaluateExpression(postFix);

document.write("Infix: " + userInput + "<br/>");
document.write("Postfix (RPN): " + postFix + "<br/>");
document.write("Result: " + result + "<br/>");


function EvaluateExpression(expression)
{
    var tokens = expression.split(/([0-9]+|[*+-\/()])/);
    var evalStack = [];

    while (tokens.length != 0)
    {
        var currentToken = tokens.shift();

        if (isNumber(currentToken))
        {
            evalStack.push(currentToken);
        }
        else if (isOperator(currentToken))
        {
            var operand1 = evalStack.pop();
            var operand2 = evalStack.pop();

            var result = PerformOperation(parseInt(operand1), parseInt(operand2), currentToken);
            evalStack.push(result);
        }
    }
    return evalStack.pop();
}

function PerformOperation(operand1, operand2, operator)
{
    switch(operator)
    {
        case '+': 
            return operand1 + operand2;
        case '-':
            return operand1 - operand2;
        case '*':
            return operand1 * operand2;
        case '/':
            return operand1 / operand2;
        default:
            return;
    }

}

function InfixToPostfix(expression)
{
    var tokens = expression.split(/([0-9]+|[*+-\/()])/);
    var outputQueue = [];
    var operatorStack = [];

    while (tokens.length != 0)
    {
        var currentToken = tokens.shift();

        if (isNumber(currentToken)) 
        {
            outputQueue.push(currentToken);
        }
        else if (isOperator(currentToken)) 
        {
            while ((getAssociativity(currentToken) == 'left' && 
                    getPrecedence(currentToken) <= getPrecedence(operatorStack[operatorStack.length-1])) ||
                   (getAssociativity(currentToken) == 'right' && 
                    getPrecedence(currentToken) < getPrecedence(operatorStack[operatorStack.length-1]))) 
            {
                outputQueue.push(operatorStack.pop())
            }

            operatorStack.push(currentToken);

        }
        else if (currentToken == '(')
        {
                operatorStack.push(currentToken);
        }
        else if (currentToken == ')')
        {
            while (operatorStack[operatorStack.length-1] != '(')
            {
                if (operatorStack.length == 0)
                    throw("Parenthesis balancing error! Shame on you!");

                outputQueue.push(operatorStack.pop());
            }   
            operatorStack.pop();        
        }   
    }  

    while (operatorStack.length != 0)
    {
        if (!operatorStack[operatorStack.length-1].match(/([()])/))
            outputQueue.push(operatorStack.pop());
        else
            throw("Parenthesis balancing error! Shame on you!");         
    }

    return outputQueue.join(" ");
}    


function isOperator(token)
{
    if (!token.match(/([*+-\/])/))
        return false;
    else 
        return true;
}


function isNumber(token)
{
    if (!token.match(/([0-9]+)/))
        return false;
    else
        return true;
}


function getPrecedence(token)
{
    switch (token)
    {
        case '^':
            return 9; 
        case '*':           
        case '/':
        case '%':
            return 8;
        case '+':
        case '-':
            return 6;
        default: 
            return -1;
    }
}

function getAssociativity(token)
{
    switch(token)
    {
        case '+':
        case '-':
        case '*':
        case '/':
            return 'left';
        case '^':
            return 'right';
    }

}

到目前为止它运行良好。如果我给它:

((5+3) * 8)

它会输出:

中缀:((5+3) * 8)
后缀 (RPN):5 3 + 8 *
结果:64

但是,我正在努力实现一元运算符,因此我可以执行以下操作:

((-5+3) * 8)

实现一元运算符(否定等)的最佳方法是什么?另外,有人对处理浮点数有什么建议吗?

最后一件事,如果有人看到我在 JavaScript 中做任何奇怪的事情,请告诉我。这是我的第一个 JavaScript 程序,我还不习惯。

【问题讨论】:

    标签: javascript algorithm shunting-yard


    【解决方案1】:

    这不在 Javascript 中,但这是我在搜索后专门为解决此问题而编写的一个库,但没有找到任何明确的答案。 这可以满足您的所有需求,甚至更多:

    https://marginalhacks.com/Hacks/libExpr.rb/

    它是一个 ruby​​ 库(以及用于检查它的测试平台),它运行修改后的分流场算法,该算法还支持一元 ('-a') 和三元 ('a?b:c') 操作。它还执行 RPN、Prefix 和 AST(抽象语法树)——您的选择,并且可以评估表达式,包括产生可以处理任何变量评估的块(某种 lambda)的能力。只有 AST 做了全套的操作,包括处理短路操作的能力(例如 '||' 和 '?:' 等),但 RPN 确实支持一元。它还有一个灵活的优先级模型,包括 C 表达式或 Ruby 表达式(不一样)所做的优先级预设。测试台本身很有趣,因为它可以创建随机表达式,然后可以 eval() 并通过 libExpr 运行以比较结果。

    它有相当多的文档/评论,因此将这些想法转换为 Javascript 或其他语言应该不会太难。

    就一元运算符而言,基本思想是您可以根据前一个标记识别它们。如果前一个标记是运算符或左括号,则“可能的一元”运算符(+ 和 -)只是一元的,并且只能用一个操作数推送。重要的是,您的 RPN 堆栈区分一元运算符和二元运算符,以便它知道在评估时要做什么。

    【讨论】:

    • 为一个 12 岁的问题添加一个新答案,忽略问题中所要求的语言,并不会尝试实际回答提出的问题,但恰好与您的新问题的关键字相匹配发布的工具只是垃圾邮件。
    • 我不同意。正如我认为其他人所做的那样,我正在寻找这个问题的确切解决方案,并且对我找不到它感到惊讶。这就是我写它的原因。这是因为我发现了这个(以及另一个 stackoverflow 问题)正是在问这个问题,并且没有答案。
    • 我已经添加了关于我如何专门修改调车场算法来解决问题的信息,你还觉得这不相关吗?
    • 这有帮助。我的部分问题是“这是一个有帮助的图书馆”,但没有任何声明说这是您自己的工作。感觉就像您编写了一个库,然后扫描 SO 寻找推广它的地方。我很高兴事实并非如此。如果您在问题中声明图书馆是为回答问题而编写的,那么问题的年龄肯定是无关紧要的。请注意,它相对easy to find JS 回答了这个问题。我会尽快查看您的代码,尽管我的 Ruby 已经生锈了。
    【解决方案2】:

    最简单的方法是让isNumber 匹配/-?[0-9]+(\.[0-9]+)?/,同时处理浮点数和负数。

    如果您确实需要处理一元运算符(例如,括号表达式的一元否定),那么您必须:

    • 使它们具有右关联性。
    • 使它们的优先级高于任何中缀运算符。
    • EvaluateExpression 中分别处理它们(创建一个单独的PerformUnaryExpression 函数,它只接受一个操作数)。
    • 通过跟踪某种状态来区分InfixToPostfix 中的一元减号和二元减号。看看'-' 在这个Python example 中是如何变成'-u' 的。

    我在another SO question 上写了一个更全面的关于处理一元减号的解释。

    【讨论】:

    • (我来自谷歌)。只是为了扩展这个答案:我通过遍历令牌列表来检测一元运算符,如果前一个令牌是运算符或不存在,则当前令牌必须是一元的。
    【解决方案3】:

    我可以通过修改一元运算符('+'和'-')来解决这个问题,以区分它们与二元运算符。

    例如,我将一元减'm'和一元加'p'称为右结合,并且它们的优先级等于指数运算符('^')。

    要检测运算符是否为一元,我只需检查运算符之前的标记是运算符还是左括号。

    这是我在 C++ 中的实现:

            if (isOperator(*token))
            {
                if (!isdigit(*(token - 1)) && *(token - 1) != ')')   // Unary check
                {
                    if (*token == '+')
                        *token = 'p';        // To distinguish from the binary ones
                    else if (*token == '-')
                        *token = 'm';
                    else
                        throw;
                }
    
                short prec = precedence(*token);
                bool rightAssociative = (*token == '^' || *token == 'm' || *token == 'p');
    
                if (!operators.empty())
                {
                    while (prec < precedence(operators.top()) || (prec == precedence(operators.top()) && !rightAssociative))
                    {
                        rpn += operators.top();
                        operators.pop();
    
                        if (operators.empty())
                            break;
                    }
                }
                operators.push(*token);
            }
    

    这里的操作符是一个栈,token 是一个中缀表达式字符串的迭代器

    (这只是操作员处理部分)

    【讨论】:

    • 抱歉评论一个较老的问题:大多数计算机语言实现一元 + 和 - 作为比指数更高的优先级;在实际数学中,一元 +/- 的优先级低于求幂。
    【解决方案4】:

    在我的 Java 实现中,我采用了以下方式:

    expression = expression.replace(" ", "").replace("(-", "(0-")
                    .replace(",-", ",0-");
            if (expression.charAt(0) == '-') {
                expression = "0" + expression;
            }
    

    【讨论】:

    • 2*-2 呢?
    【解决方案5】:

    我的建议是这样的。不要将“-”作为算术运算符处理。将其视为“符号”运算符。或将其视为整个操作数的一部分(即其符号)。我的意思是每次遇到“-”时,只需将其后面的操作数乘以-1,然后继续读取下一个标记。 :) 我希望这会有所帮助。只是一个简单的想法......

    【讨论】:

    • 但是如果您使用其他一些运算符,例如 sin 或 sqrt 怎么办?做类似 sin(3 + 4) 的事情真的很棘手。
    • 就手头的问题而言,这不是问题的一部分.. :)
    • 是的,我最近才完成了这个实现,它对我来说效果很好。
    • sin(3 + 4) 转到 3 4 + sin7 sin,然后不管 sin(7) 是什么。如果您遵循调车场算法,则一点也不棘手。
    【解决方案6】:

    当我需要支持这个时,我在中间阶段做了这个。我首先生成所有表达式词位的列表,然后使用辅助函数来提取运算符和操作数,并且“获取操作数”函数只要看到一元运算符就简单地消耗两个词位。

    不过,如果您使用另一个字符来表示“一元减号”,这真的很有帮助。

    【讨论】:

      【解决方案7】:

      要处理浮点数,您可以将(数字部分)正则表达式更改为:

      /([0-9]+\.?[0-9]*)/
      

      所以最终的正则表达式将是:

      /([0-9]+\.?[0-9]*|[*+-\/()])/
      

      对于处理一元减号运算符,您可以将其更改为另一个字符,例如“u”。 (As it is explained here by -TGO)

      我根据给定的链接编写的用于处理一元减号运算符的 javascript 代码是:

      // expr is the given infix expression from which, all the white spaces has been
      // removed.(trailing and leading and in between white space characters)
      const operators = ['+', '*', '-', '/', '^'];
      const openingBrackets = ['(', '[', '{'];
      let exprArr = Array.from(expr);
      // Since strings are immutable in js, I am converting it to an array for changing 
      // unary minus to 'u'
      for (let i = 0; i < expr.length; i++) {
          if (expr[i] === '-') {
              if (i === 0) {
                  exprArr[i] = 'u';
              } else if (operators.includes(expr[i - 1])) {
                  exprArr[i] = 'u';
              } else if (openingBrackets.includes(expr[i - 1])) {
                  exprArr[i] = 'u';
              } else {
                  // '-' is not a unary operator
                  // it is a binary operator or we have the wrong expression, so...
                  if (!openingBrackets.includes(expr[i + 1]) && !/[0-9]/.test(expr[i + 1])) {
                      throw new Error("Invalid Expression...");
                  }
              }
          }
      }
      // And finally converting back to a string.
      let expr2 = exprArr.join('');
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-07-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-09-29
        • 1970-01-01
        相关资源
        最近更新 更多