2

My infix to postfix algorithm using stack:

static int Prec(char ch) {
    switch (ch) {
    case '+':
    case '-':
        return 1;
    case '*':
    case '/':
        return 2;
    case '^':
        return 3;
    }
    return -1;
}

static String infixToPostfix(String exp) {
    String result = new String("");
    Stack<Character> stack = new Stack<>();
    for (int i = 0; i < exp.length(); ++i) {
        char c = exp.charAt(i);
        if (Character.isLetterOrDigit(c))
            result += c;
        else if (c == '(')
            stack.push(c);
        else if (c == ')') {
            while (!stack.isEmpty() && stack.peek() != '(')
                result += stack.pop();
            stack.pop();
        } else {
            while (!stack.isEmpty() && Prec(c) <= Prec(stack.peek())) {
                result += stack.pop();
            }
            stack.push(c);
        }
    }
    while (!stack.isEmpty()) {
        if (stack.peek() == '(')
            return "Invalid Expression";
        result += stack.pop();
    }
    return result;
}

Input: K+L-M*N+(O^P)*W/U/V*T+Q^J^A

Expected Output: KL+MN*-OP^W*U/V/T*+QJA^^+

Actual Output: KL+MN*-OP^W*U/V/T*+QJ^A^+

  • If current operator and operator at top of stack have the same precedence then check their associativity,
  • If associativity of operators is right to Left, then simply push operator onto the stack.
  • If associativity of operators is left to right, then pop operator from stack and check for associativity again for current operator and operator at top of stack.

The difference in the expected and actual output is when the sub-expression ^J^A is evaluated. When character Q is reached, the stack contains ['+']. I output the operand Q and move to the next character in the expression. With respect to the above rule, the following should happen:

  • ^ is the next character after Q. Since ^ has a higher precedence than +, I pushed it into the stack, ['+','^'] and moved pointer to J.
  • Output J as it's an operand and move pointer.
  • Pointer is now at ^. Since top of stack also contains a ^, they have the same precedence so we need to check associativity rule which is right to left. Thus, we push ^ into the stack. So the stack looks something like ['+','^','^'].
  • Pointer is now at last character A so just output it.
  • Since we have reached end of expression, we can start popping out the operators from the stack so the postfix form of subexpression will look like QJA^^+.

However, my code doesn't work for associativity. Can someone explain how associativity can be handled in the above implementation?

user207421
  • 305,947
  • 44
  • 307
  • 483
nehacharya
  • 925
  • 1
  • 11
  • 31
  • my guess:instead of checking precedence with `<=`, you must split it into `<` and `==`, last case must include checking associativity (maybe using similar method like `Prec`) ((Note: by convention method names start with lowercase in Java, classes with uppercase - just convention, but almost all developers are using it)) –  Mar 01 '21 at 10:09
  • 1
    basically, pseudo code, something like `( prec(op) < prec(peek) || ( prec(op) == prec(peek) && leftToRight(peek) ) )` –  Mar 01 '21 at 10:27
  • By the way, `new String("")` is a bit pointless and should be just `""`. Also, it's preferable to use `StringBuilder` instead of appending to a String in a loop. And instead of `Stack`, it's preferable to use `ArrayDeque` (as the documentation for Stack says). – k314159 Mar 01 '21 at 17:32
  • @k314159 yeah you're right. I'll make that change. Thanks! – nehacharya Mar 01 '21 at 19:02
  • @k314159 `StringBuilder` is not needed (recommended?) with newer Java versions... compiler uses other (better) methods –  Mar 02 '21 at 10:05
  • @user15244370 You may be thinking about the fact that the compiler uses StringBuilder internally when you concatenate using `+`, but that is _only when it can_. Inside a loop, for example, you should still use StringBuilder. A good explanation is at https://stackoverflow.com/a/24571563/13963086 – k314159 Mar 02 '21 at 10:11
  • 1
    @k314159 - no, the compiler does not use `StringBuilder` any more - see [`StringConcatFactory`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/lang/invoke/StringConcatFactory.html) (the question you linked is 2014, and if you go back all the duplicates, you will find that [comment](https://stackoverflow.com/questions/1532461/stringbuilder-vs-string-concatenation-in-tostring-in-java#comment114350855_1532499) or go directly to [JEP-280](https://openjdk.java.net/jeps/280)) - or just decompile (`javap -p -c`) a simple test code –  Mar 02 '21 at 10:13
  • @user15244370 thanks for that info. So this is from Java 9 onwards, and it applies to simple concatenations like `String s = arg1 + "-" + arg2;`. However, you should still use StringBuilder when appending in a loop, or when appending conditionally. See https://dev.to/jitterted/stop-trying-to-outsmart-the-java-compiler-1876 section titled "Not for loops". – k314159 Mar 02 '21 at 10:37
  • you are right, but a year old article, based partially on even older youtube videos... what magnitude is the difference? or just micro-optimization? I doubt it will be any sensible difference for the user (this code is probably not meant to run thousand of iterations) –  Mar 02 '21 at 10:45

2 Answers2

2

It is an easy fix. You need to update the while loop condition from:

while (!stack.isEmpty() && Prec(c) <= Prec(stack.peek()))

To:

while (!stack.isEmpty() && (prec(c) < prec(stack.peek()) || (prec(c) == prec(stack.peek()) && isLeftToRightAssociative(stack.peek()))))

Here is the method isLeftToRightAssociative():

public static boolean isLeftToRightAssociative(Character operator)
{
    //You can also use return operator != '^' as an alternative to the if-else given below
    if (operator == '^') return false;
    else return true;      
}

I should add that your code will not produce correct output if the user inputs an expression which contains unary operators. If you will be working with unary operators, you should update your code.

AKSingh
  • 1,535
  • 1
  • 8
  • 17
  • 1
    `if (cond) return false; else return true;` is just `return ! cond;` - in that case even easier `return operator != '^';` (some déjà vu here (:-| ) –  Mar 02 '21 at 10:22
  • @user15244370 Yes the `return statement` in the `isLeftToRightAssociative()` can be reduced as you said. *I wrote it in this manner as it is a bit more simpler to understand.* More beginner friendly. – AKSingh Mar 02 '21 at 10:25
  • I disagree... sure opinion based...more *thinking steps* needed to understand `if` version... but again IMHO (and using `if` is not wrong) –  Mar 02 '21 at 10:29
  • @user15244370 I also edited my answer to mention that `return operator != '^'` can also be used *as an alternative way of returning the same answer.* – AKSingh Mar 02 '21 at 10:31
0

Easy Fix - Just before else statement add another else if

else if (c==stack.peek() and c=='^')
{ stack.push(c); }