4

I have written shunting yard algorithm in JS which works fine for almost all the scenarios, however if I have a negative number scenario then it fails, say for example if I give this expression 9-(3*(-6)) then it won't give a result...Any hint would be much appreciated ...I don't want to use regex. I have written my own expression parser instead.

my code:-

http://jsfiddle.net/min2bro/8ZvGh/20/

     // ========================== Converting string into Array of opeartors & operands including brackets also======
    // for example: if you enter the expression: 9-(5*2)+(7.66/3.21)*3 
    // this would return ['9','-','5','*','2','+','7.66','/','3.21','*','3']


    output = prompt("Enter the expression");


var result = [];
var str = "";
var temp = [];
var expression = [];



  for (i = 0; i < output.length; ++i)

  { 

      if(output[i] != "*" && output[i] != "+" && output[i] != "/" && output[i] != "-" )

       temp.push(output[i]);

      if(output[i] == "*" || output[i] == "+" || output[i] == "-" || output[i] == "/")

      { 
         for(var j = 0; j<= temp.length-1 ; j++ )
         {

             if (temp[j] == '(' || temp[j] == ')')
             { 
                 expression.push(temp[j])
             }
             else
             {
                 str += temp[j];
                 if (temp[j+1] == ")")
                 { expression.push(str);
                    str = "";
                 }
             }
         }

         var temp = [];  
         if (str!="")
         {
             expression.push(str);
         }
         expression.push(output[i]);

      }       

      str = "";

  } 

for(var n = 0 ; n<= temp.length-1 ; n++ )
{

                 if (temp[n] == '(' || temp[n] == ')')
             { 
                 expression.push(temp[n])
             }
             else
             {
                 str += temp[n];
                 if (temp[n+1] == ")")
                 { expression.push(str);
                    str = "";
                 }
             }


}
if (str!="")
         {
             expression.push(str);
         }











// ========================== Converting expression array into output array as defined in shunting algorithm
// for example: if you enter the expression: 9-(5*2)+(7.66/3.21)*3 
// this would return [9,5,2,*,-,7.66,3.21,/,3,*,+]
//==============================================================================

var output = [];   
var stack = [];
var precedence = {'+': 1,'-': 1,'*': 2,'/': 2,'(': 0};

for(var i = 0; i <= (expression.length-1) ; i++)
{
    if(!isNaN(expression[i]))
    {
      output.push((expression[i]));   
    }
    else if(expression[i] == "*" || expression[i] == "/" || expression[i] == "+" || expression[i] == "-" || expression[i] == "(" || expression[i] == ")")
    {
        if(stack == "" && expression[i] != ")")
       {
           stack.push(expression[i]);
       }
        else if(precedence[expression[i]] > precedence[stack[(stack.length -1)]])
       {
        stack.push(expression[i]);
       }
        else if((precedence[expression[i]] <= precedence[stack[stack.length -1]]))
        {   
            if(expression[i] == "(")
            {
                stack.push(expression[i]);
            }
            if(stack[stack.length-1]!="(")
            { 
            for(var k = (stack.length-1); k >= 0 ; k--)  
              { 
                  output.push(stack[k]);
                stack.pop(stack[k]);
              }
                stack.push(expression[i]);
            }
         }

if(expression[i] == ")")
{
    for(var j = (stack.length-1); j > 0 ; j--)
    {  
        if(stack[j]!="(")
          output.push(stack[j]);
          stack.pop(stack[j]);
    }

}
}

    //alert(stack)
    if(i == expression.length-1 && expression[i] != ")")
{
    //alert(stack);
    for(var j = (stack.length-1); j >= 0 ; j--)
    {  
        if(stack[j]!="(")
       output.push(stack[j]);
        stack.pop();
    }

}

}

    //alert(stack);
    for(var j = (stack.length-1); j >= 0 ; j--)
    {  
        if(stack[j]!="(")
       output.push(stack[j]);
    }




//============ Calculate the result===================

var result = [];

  for (i = 0; i < output.length; ++i)
  { 
    t = output[i];
      //alert(t);
    if (!isNaN(t))
      result.push(t);
    else if (t == "(" || result.length < 2)
      return false;
    else 
    {
       //alert(result);
      var rhs = result.pop();
       //alert(rhs);
      var lhs = result.pop();
      // alert(rhs);
      if (t == "+") result.push(parseFloat(lhs) + parseFloat(rhs));
      if (t == "-") result.push(parseFloat(lhs) - parseFloat(rhs));
      if (t == "*") result.push(parseFloat(lhs) * parseFloat(rhs));
      if (t == "/") result.push(parseFloat(lhs) / parseFloat(rhs));
    }
  }
alert(result);
Guy Coder
  • 24,501
  • 8
  • 71
  • 136
min2bro
  • 554
  • 2
  • 9
  • 19
  • 2
    I think you're saying your parser doesn't cope with unary operators, correct? – Lee Taylor Jun 17 '13 at 16:08
  • @Lee you are correct.. – min2bro Jun 17 '13 at 16:11
  • 1
    I should imagine that if the token before the unary minus or plus is not a number then imply that the minus or plus "belongs" to the number in front. How does your algorithm cope with malformed entry? e.g. 5+*9, **9, 9-, etc. – Lee Taylor Jun 17 '13 at 16:22

3 Answers3

3

In my opinion its not the right way to handle negative numbers during the tokenization process as mentionded by 'Aadit M Shah'

I would recomend to handle the unary + or - within the shunting-yard algorythm. Just replace the unary + or - with a different sign (in my case 'p' or 'm') and handle them during the evaluation of the postfix notation (or RPN).

You can find my C# implementation here on GitHub

MacTee
  • 63
  • 1
  • 8
2

A solution to your problem could be rewriting the entry 9-(3*(-6)) to 9-(3*(0-6)) to make the - operator binary. Just replace each (- in the string with (0-.

gab06
  • 578
  • 6
  • 23
2

Alright, so I don't know what's the problem with your code. It's not well formatted and it's too long. So I didn't read it. Nevertheless, here's how I would have written your program:

I'd split the program into a lexical analysis and a parsing phase. This makes your program more modular and easier to understand. I've already written a generic lexer and a shunting yard parser. So I'll use those to write the program.

First up, the lexical analyzer (I know that you didn't want to use regex and that you wrote your own expression parser, however this is the stuff regular expressions are good for, so):

const lexer = new Lexer();

lexer.addRule(/\s+/, () => {}); // skip whitespace

lexer.addRule(/[\+\-\*\/\(\)]/, lexeme => lexeme); // punctuators: + - * / ( )

lexer.addRule(/\-?(?:0|[1-9]\d*)(?:\.\d+)?/, lexeme => +lexeme); // numbers

Next we have the shunting yard parser:

const left1 = { associativity: "left", precedence: 1 };

const left2 = { associativity: "left", precedence: 2 };

const parser = new Parser({ "+": left1, "-": left1, "*": left2, "/": left2 });

Then we connect the lexer to the parser:

Array.fromIterator = it => Array.from({ [Symbol.iterator]: () => it });

const step = value => ({ done: value === undefined, value });

const parse = input => {
    lexer.setInput(input);
    const next = () => step(lexer.lex());
    const tokens = Array.fromIterator({ next });
    return parser.parse(tokens);
};

Now all you need to do is call the parse function as follows:

const output = parse("9 - (5 * 2) + (7.66 / 3.21) * 3");

console.log(output); // [9, 5, 2, "*", "-", 7.66, 3.21, "/", 3, "*", "+"]

See the output for yourself.

const lexer = new Lexer();

lexer.addRule(/\s+/, () => {}); // skip whitespace

lexer.addRule(/[\+\-\*\/\(\)]/, lexeme => lexeme); // punctuators: + - * / ( )

lexer.addRule(/\-?(?:0|[1-9]\d*)(?:\.\d+)?/, lexeme => +lexeme); // numbers

const left1 = { associativity: "left", precedence: 1 };

const left2 = { associativity: "left", precedence: 2 };

const parser = new Parser({ "+": left1, "-": left1, "*": left2, "/": left2 });

Array.fromIterator = it => Array.from({ [Symbol.iterator]: () => it });

const step = value => ({ done: value === undefined, value });

const parse = input => {
    lexer.setInput(input);
    const next = () => step(lexer.lex());
    const tokens = Array.fromIterator({ next });
    return parser.parse(tokens);
};

const output = parse("9 - (5 * 2) + (7.66 / 3.21) * 3");

console.log(output); // [9, 5, 2, "*", "-", 7.66, 3.21, "/", 3, "*", "+"]
<script src="https://rawgit.com/aaditmshah/lexer/master/lexer.js"></script>
<script src="https://rawgit.com/aaditmshah/6683499/raw/875c795ec9160e095a4030e82d5a6e3416d9fdc7/shunt.js"></script>

It also correctly parses negative numbers:

const output = parse("9 - (3 * (-6))");

console.log(output); // [9, 3, -6, "*", "-"]

See the demo.

const lexer = new Lexer();

lexer.addRule(/\s+/, () => {}); // skip whitespace

lexer.addRule(/[\+\-\*\/\(\)]/, lexeme => lexeme); // punctuators: + - * / ( )

lexer.addRule(/\-?(?:0|[1-9]\d*)(?:\.\d+)?/, lexeme => +lexeme); // numbers

const left1 = { associativity: "left", precedence: 1 };

const left2 = { associativity: "left", precedence: 2 };

const parser = new Parser({ "+": left1, "-": left1, "*": left2, "/": left2 });

Array.fromIterator = it => Array.from({ [Symbol.iterator]: () => it });

const step = value => ({ done: value === undefined, value });

const parse = input => {
    lexer.setInput(input);
    const next = () => step(lexer.lex());
    const tokens = Array.fromIterator({ next });
    return parser.parse(tokens);
};

const output = parse("9 - (3 * (-6))");

console.log(output); // [9, 3, -6, "*", "-"]
<script src="https://rawgit.com/aaditmshah/lexer/master/lexer.js"></script>
<script src="https://rawgit.com/aaditmshah/6683499/raw/875c795ec9160e095a4030e82d5a6e3416d9fdc7/shunt.js"></script>

In addition it handles precedence and associativity rules to get rid of redundant parentheses:

const output = parse("9 - 3 * -6");

console.log(output); // [9, 3, -6, "*", "-"]

The demo.

const lexer = new Lexer();

lexer.addRule(/\s+/, () => {}); // skip whitespace

lexer.addRule(/[\+\-\*\/\(\)]/, lexeme => lexeme); // punctuators: + - * / ( )

lexer.addRule(/\-?(?:0|[1-9]\d*)(?:\.\d+)?/, lexeme => +lexeme); // numbers

const left1 = { associativity: "left", precedence: 1 };

const left2 = { associativity: "left", precedence: 2 };

const parser = new Parser({ "+": left1, "-": left1, "*": left2, "/": left2 });

Array.fromIterator = it => Array.from({ [Symbol.iterator]: () => it });

const step = value => ({ done: value === undefined, value });

const parse = input => {
    lexer.setInput(input);
    const next = () => step(lexer.lex());
    const tokens = Array.fromIterator({ next });
    return parser.parse(tokens);
};

const output = parse("9 - 3 * -6");

console.log(output); // [9, 3, -6, "*", "-"]
<script src="https://rawgit.com/aaditmshah/lexer/master/lexer.js"></script>
<script src="https://rawgit.com/aaditmshah/6683499/raw/875c795ec9160e095a4030e82d5a6e3416d9fdc7/shunt.js"></script>

Hope that helps.

Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • I don't think this will work for -(3+3) or something similar. – Siddharth Agrawal Dec 23 '20 at 06:01
  • It parses the expression `-(3+3)` correctly. – Aadit M Shah Dec 23 '20 at 06:08
  • What does it parse it to? U posted a js fiddle before, which outputted a postfix expression that would evaluate as either "Invalid Expression" or an incorrect answer if you try to convert it using https://scanftree.com/Data_Structure/prefix-postfix-infix-online-converter. Perhaps you edited your post to account for it now? As far as I have read, you have to take unary operators as a separate operator to be able to distinguish between unary operations and binary operations in the shunting yard and postfix evaluation algorithms. – Siddharth Agrawal Dec 23 '20 at 09:12
  • It may also be possible that a different postfix evaluator algorithm was used by the website and you, but in that case, how should I evaluate "3 3 + -" postfix expression? – Siddharth Agrawal Dec 23 '20 at 09:18
  • The expression `-(3+3)` is parsed to `[3, 3, "+", "-"]`. Note that we don't distinguish between the negation operator and the subtraction operator. Hence, there may be more than one way to interpret a given parse tree. In addition, we haven't defined the precedence and associativity rules of negation. Hence, it might not always produce the correct ordering of tokens. If you want to implement negation properly, just add additional rules to the lexer or parser. – Aadit M Shah Dec 23 '20 at 09:27
  • Yes, that is why it is probably better to use a method similar to one pointed out by @MacTee and assign 'P' and 'M' for unary operators and '+' and '-' for binary operators. Then you can make a slight change to the Postfix evaluator so that if the expression is a uniary + or - it pops the value off of the output stack and pushes back the negative of that value or the same value. With this approach, one can also evaluate 3---+-+---2 (which is equal to 1) which I don't think will work with your approach (though I may be wrong) – Siddharth Agrawal Dec 23 '20 at 09:57
  • Sure, go ahead. It's out of the scope of this question though, so I'm not going to modify my answer. – Aadit M Shah Dec 23 '20 at 10:02
  • Oh yes, that's fine. I don't expect you to, and yours provides a viable solution for most cases. I just needed something that works for all cases, any mathematical expression. Wouldn't be a true algorithm if it cannot process all forms of valid input of which ``-(3+3)`` and ``3---+-+---2`` are perfectly valid inputs. Might write my own answer to this and other questions later perhaps. – Siddharth Agrawal Dec 23 '20 at 10:08