javaandroidcalculatorshunting-yard

Shunting Yard Algorithm Based Scientific Calculator


I'm developing a scientific calculator in android studio using java. I have used Streatergy Design Pattern for handling operations as well as Shunting Yard Algorithm for the calculate function where it follows the BODMAS rule.

I have two questions.

  1. The BODMAS rule works fine but it dose not follow left to right rule, if there are same precedence operation.
  2. How do I include the "cos" , "sin", etc. functions in this? which should follow the BODMAS rule.

Bellow is the code that I have developed so far. My concern is the algorithm implemented based on char array. If i were to identify "cos" I have search for letter 'c' or any other functions first letter and go through the next elements and find the full function name. is there another way to do this? Also how dose it works on Left to right rule with these scientific functions.

Here is the code that I have developed so far.

Any advise is appreciated.

Thanks.

public class ScientificCalculator {
public Operation operation;

public ScientificCalculator(Operation operation){
  this.operation = operation;
}

public double calculate(String exp) {
  char[] tokens = exp.toCharArray();
  Queue values = new LinkedList<>();
  Stack ops = new Stack();

  for (int i = 0; i < tokens.length; i++) {
    if (tokens[i] == ' ') {
      continue;
    }
    else if (Character.isDigit(tokens[i])) {
      StringBuffer sbuf = new StringBuffer();
      while (i < tokens.length && Character.isDigit(tokens[i])) {
        sbuf.append(tokens[i]);
        if ((i + 1) < tokens.length && Character.isDigit(tokens[i+1])) {
          i++;
        } else {
          break;
        }
      }
      values.add(Double.parseDouble(sbuf.toString()));
    } else if (tokens[i] == 'x' || tokens[i] == '-' || tokens[i] == '/' || tokens[i] == '+') {
      if (ops.isEmpty()) {
      ops.push(tokens[i]);
      continue;
    }
      char op1 = ops.peek();
      boolean hasHighPrecedence = hasPrecedence(op1, tokens[i]);
      if (hasHighPrecedence) {
      char op = ops.pop();
      values.add(op);
      ops.push(tokens[i]);
      } else {
        ops.push(tokens[i]);
      }
    } else if (tokens[i] == '(') {
        ops.push(tokens[i]);
    } else if (tokens[i] == ')') {
        while (ops.peek() != '(') {
          values.add(ops.pop());
    }
    ops.pop();
    }
  }
  while (!ops.isEmpty()) {
    values.add(ops.pop());
  }
  Stack numStack = new Stack<>();
  while (!values.isEmpty()) {
    Object val = values.poll();
    if (val instanceof Character) {
      char v = (Character) val;
      if (v == 'x' || v == '-' || v == '/' || v == '+') {
        double num2, num1;
        num1 = numStack.pop();
        num2 = numStack.pop();
        double ans = applyOp(v, num1, num2);
        numStack.push(ans);
      }
    } else {
    double num = (double) val;
    numStack.push(num);
    }
  }
  return numStack.pop();
}
public static double applyOp(char op, double b, double a) {
  switch (op) {
    case '+':
      return new addition().calculate(a,b);
    case '-':
      return new subtraction().calculate(a,b);
    case 'x':
      return new multiplication().calculate(a,b);
    case '/':
      if (b == 0)
        throw new
              IllegalArgumentException("Cannot divide by zero");
      return new division().calculate(a,b);
    }
    return 0;
  }

  public static boolean hasPrecedence(char op1, char op2) {
    if ((op1 == 'x' || op1 == '/') && (op2 == '+' || op2 == '-')){
      return true;
    } else {
      return false;
    }
  }
}

Solution

  • You can make the shunting yard algorithm follow the left or right associative rules. Most common operators +, -, *, / are left associative, but some, like assignment are typically right associative. So x - y - z is interpreted as (x - y) - z and x = y = z is x = (y = z), y is set to value of z, and x is then set to this value.

    Lets consider what should happen if we try and parse 3 - 4 + 5, which should be interpreted as (3 - 4) + 5, giving 4, and not 3 - (4 + 5) giving -6. + and - are normally given the same precedence.

    The output would then be 3 4 - 5 +

    We see this in the Wikipedia example, https://en.wikipedia.org/wiki/Shunting_yard_algorithm#The_algorithm_in_detail

    while (
            there is an operator o2 at the top of the operator stack which is not a left parenthesis, 
            and (o2 has greater precedence than o1 or (o1 and o2 have the same precedence and o1 is left-associative))
        ):
            pop o2 from the operator stack into the output queue
    push o1 onto the operator stack
    

    In your code

    boolean hasHighPrecedence = hasPrecedence(op1, tokens[i]);
    if (hasHighPrecedence || 
        ( hasEqualPrecedence(op1,tokens[i]) && isLeftAssociative(op1) )) {
      char op = ops.pop();
      values.add(op);
      ops.push(tokens[i]);
    } else {
        ops.push(tokens[i]);
    }
    

    For the second part, function calls should be treated the same as other terminal symbols (variables or numbers) for the purposed of the shunting yard algorithm. In my code I have a method prefixSuffix() who's job is to parse all expressions like 5, x, ++x, x++, function calls and bracketed expressions. The shunting-yard part is just used to handle binary operators. With function calls and bracketed expressions their arguments are parsed with recursive calls to the shunting yard part. This is explained in other SO answers on the topic.