如何修改我的调车场算法以使其接受一元运算符?

发布于 2024-08-08 11:59:41 字数 4230 浏览 3 评论 0 原文

我一直致力于在课堂上用 JavaScript 实现调车场算法。

这是我到目前为止的工作:

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 程序,我还不习惯。

I've been working on implementing the Shunting-Yard Algorithm in JavaScript for class.

Here is my work so far:

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';
    }

}

It works fine so far. If I give it:

((5+3) * 8)

It will output:

Infix: ((5+3) * 8)
Postfix (RPN): 5 3 + 8 *
Result: 64

However, I'm struggling with implementing the unary operators so I could do something like:

((-5+3) * 8)

What would be the best way to implement unary operators (negation, etc)? Also, does anyone have any suggestions for handling floating point numbers as well?

One last thing, if anyone sees me doing anything weird in JavaScript let me know. This is my first JavaScript program and I'm not used to it yet.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

满地尘埃落定 2024-08-15 11:59:41

最简单的事情是使 isNumber 匹配 /-?[0-9]+(\.[0-9]+)?/,处理浮点和负数。

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

  • 使它们右结合。
  • 使它们的优先级高于任何中缀运算符。
  • EvaluateExpression 中单独处理它们(创建一个单独的 PerformUnaryExpression 函数,该函数只接受一个操作数)。
  • 通过跟踪某种状态来区分 InfixToPostfix 中的一元减法和二进制减法。在此 Python 示例

我在另一个SO问题上写了更全面的解释来处理一元减号< /a>.

The easiest thing would be to make isNumber match /-?[0-9]+(\.[0-9]+)?/, handling both floating points and negative numbers.

If you really need to handle unary operators (say, unary negation of parenthesized expressions), then you have to:

  • Make them right-associative.
  • Make them higher precedence than any of the infix operators.
  • Handle them separately in EvaluateExpression (make a separate PerformUnaryExpression function which only takes one operand).
  • Distinguish between unary and binary minus in InfixToPostfix by keeping track of some kind of state. See how '-' is turned into '-u' in this Python example.

I wrote up a more thorough explanation of handling unary minus on another SO question.

只等公子 2024-08-15 11:59:41

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

my suggestion is this. don't handle the '-' as an arithmetic operator. treat it as a 'sign' operator. or treat it as if it's a part of the whole operand (i.e. its sign). what i mean is that everytime you encounter '-', you just have to multiply the operand after it by -1, then proceed to read the next token. :) i hope that helps. just a simple thought...

寒尘 2024-08-15 11:59:41

我可以通过修改一元运算符(“+”和“-”)以将它们与二进制运算符区分开来解决这个问题。

例如,我将一元减“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);
        }

这里运算符是一个堆栈,令牌是中缀表达式字符串的迭代器

(这只是运算符处理部分)

I could solve this problem by modifying unary operators('+' and '-') to distinguish them from the binary ones.

For example, I called the unary minus 'm' and unary plus 'p', making them right-assocative and their precedence equal to the exponent operator('^').

To detect if the operator is unary I simply had to check if the token before the operator was an operator or an opening bracket.

This is my implementation in 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);
        }

Here operators is a stack and token is an iterator to the infix expression string

(This just the operator handling part)

別甾虛僞 2024-08-15 11:59:41

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

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

When I needed to support this, I did this in an intermediate stage. I started by generating a list of all expression lexemes, then used helper functions to extract operators and operands and the "get operand" function simply consumed two lexemes whenever it saw a unary operator.

It really helps if you use another character to signify "unary minus", though.

此生挚爱伱 2024-08-15 11:59:41

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

https://marginalhacks.com/Hacks/libExpr.rb/< /a>

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

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

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

This isn't in Javascript, but here is a library I wrote to specifically solve this problem after searching and not finding any clear answers.
This does all you want and more:

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

It is a ruby library (as well as a testbench to check it) that runs a modified shunting yard algorithm that also supports unary ('-a') and ternary ('a?b:c') ops. It also does RPN, Prefix and AST (abstract syntax trees) - your choice, and can evaluate the expression, including the ability to yield to a block (a lambda of sorts) that can handle any variable evaluation. Only AST does the full set of operations, including the ability to handle short-circuit operations (such as '||' and '?:' and so on), but RPN does support unary. It also has a flexible precedence model that includes presets for precedence as done by C expressions or by Ruby expressions (not the same). The testbench itself is interesting as it can create random expressions which it can then eval() and also run through libExpr to compare results.

It's fairly documented/commented, so it shouldn't be too hard to convert the ideas to Javascript or some other language.

The basic idea as far as unary operators is that you can recognize them based on the previous token. If the previous token is either an operator or a left-paren, then the "unary-possible" operators (+ and -) are just unary and can be pushed with only one operand. It's important that your RPN stack distinguishes between the unary operator and the binary operator so it knows what to do on evaluation.

醉生梦死 2024-08-15 11:59:41

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

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

In my Java implementation I did it in next way:

expression = expression.replace(" ", "").replace("(-", "(0-")
                .replace(",-", ",0-");
        if (expression.charAt(0) == '-') {
            expression = "0" + expression;
        }
遗失的美好 2024-08-15 11:59:41

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

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

因此最终的正则表达式将是:

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

为了处理一元减号运算符,您可以将其更改为另一个字符,例如“u”。
正如这里所解释的 -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('');

To handle floating point numbers you can change your (number part of) regex to:

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

so the final regex would be:

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

And for handling unary minus operator, you can change it to another character like 'u'.
(As it is explained here by -TGO)

The javascript code that i wrote for handling unary minus operator based on the link given is:

// 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('');
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文