1

Hi I have been working on my Java calculator. It now puts all the operands and operators in different arrays and I also have an array with the priority of each operator.

For example * and / are 1 and + and - are 2.
So if my operator array holds '[/, +, +, *]'
My priority array holds '[1, 2, 2, 1]'

The part I have come stuck on is I now need to make sure my calculator can reorder the operators so I can work through my calculateResult and make sure the operators are in the correct order.

The part I am asking for help with is calculate result. I cant figure out how to go about doing this so that it calculates it in the correct order.

import java.util.*;
public class stringCalculator {

    //String containing the input from the user
    private String userinput;
    //List to store the operators in
    private ArrayList<String> calcOperator = new ArrayList<String>();
    //List to store the operands in
    private ArrayList<Integer> calcOperand = new ArrayList<Integer>();
    //Array to store all the integers in
    private String[] integers;
    //Array to store all the operators in
    private String[] operators;
    //Array to store the priority value
    private String[] priorityList;

    public stringCalculator(String userinput){
        this.userinput = userinput;
        //System.out.println(userinput);
        integers = userinput.split("[+-/*///]");
        operators = userinput.split("\\d+");
    }   

    //This function will check the input and return true if the user enters a correct expression.
    public boolean checkInput(){
        boolean show = userinput.matches("[-+/*0-9]+");
        return show;
    }

    //This function will add any numbers in the string to the calcOperand array.
    //and any operators to the calcOperator field.
    public void parseInput(String[] item){
        for (String op : item){
            if (op.matches("\\d+")){
                calcOperand.add(Integer.parseInt(op));
            }
            //operators go into calcOperators.
            else if (op.equals("+")||op.equals("-")||op.equals("*")||op.equals("/")){
                calcOperator.add(op);
            }
            else{//everything else is ignored and left. 
            }
        }
    }

    //Function to calculate the priority of each operator.
    //For example * and / will be 1, and + and - will be 2.
    public void calculatePriority(){
        priorityList = calcOperator.toArray(new String[calcOperator.size()]);
        for (int i = 0; i<priorityList.length; i++){
            if (priorityList[i].equals("+")){
                priorityList[i] = "2";
            }else if (priorityList[i].equals("-")) {
                    priorityList[i] = "2";
            }else if (priorityList[i].equals("/")){
                priorityList[i] = "1";
            }else if (priorityList[i].equals("*")){
                priorityList[i] = "1";
            }else{
                System.out.println("error");
            }
        }
    }
    public void printPri(){

        for (String s : priorityList)
            System.out.print(s +",");
    }

    //Function to show the result of the expression.
    public void calculateResult(){
        if(checkInput()){
            parseInput(integers);
            parseInput(operators);
            System.out.println("Operands: " + calcOperand);
            System.out.println("Operators: " + calcOperator);
            calculatePriority();
            System.out.print("Priority: ");
            printPri();
        }else{
            System.out.println("Please enter a valid input!");

        }
    }

}

3 Answers 3

1

A very basic example of one way to generate a tree of the operations.

I've nested the classes within a single Test class (since I didn't want to faff around with extra files) but it should be simple enough to unbundle).

  • MathNode is an abstract class representing a node in the tree;
  • ElementNode is a sub-class of MathNode representing a number value (and a leaf of the tree);
  • SumNode is a sub-class of MathNode representing an operation;
  • tokenize() iterates through the string finding operations (or the end of the string) and the preceding number and creates the appropriate tokens and appends them to an array.
  • makeTree() iterates through the array of tokens and when the current operation being searched for is found sets the left and right nodes of the tree to be the immediately preceding and succeeding tokens (removing those tokens from the array once added to the tree).
  • Running tokenize() then makeTree() four times, one for each of the /, *, - and then + operations, will build the tree with the single Node left in the array of tokens being the root of the tree.

There is more error checking that can be included and I'm sure the tree building can be made significantly more efficient (and not to require four passes) but, hopefully, this will give you an idea of how to proceed.

The code:

import java.util.ArrayList;

public class Test
{
    public static enum Operation { MULTIPLY, DIVIDE, ADD, SUBTRACT };
    abstract static class MathNode {
        public abstract double calc();
        public abstract String toString();
        public abstract boolean set( final MathNode left, final MathNode right, final Operation op );
    }
    static class ElementNode extends MathNode {
        private final double value;
        public ElementNode( final double v ) {
            this.value = v;
        }
        public double calc() {
            return value;
        }
        public String toString() {
            return Double.toString( value );
        }
        public boolean set( final MathNode left, final MathNode right, final Operation op ){
            return false;
        }
    }
    static class SumNode extends MathNode {
        public MathNode left = null;
        public MathNode right = null;
        public final Operation op;
        public SumNode( final Operation op ){
            this.op = op;
        }
        public boolean set( final MathNode left, final MathNode right, final Operation op ){
            if ( this.op == op )
            {
                this.left = left;
                this.right = right;
                return true;
            }
            return false;
        }
        public double calc() {
            final double l = left  == null ? 0 : left.calc();
            final double r = right == null ? 0 : right.calc();
            switch ( this.op ){
            case MULTIPLY:  return l * r;
            case DIVIDE:    return l / r;
            case SUBTRACT:  return l - r;
            default:        return l + r;
            }
        }
        public String toString(){
            final String l = left  == null?"0":left.toString();
            final String r = right == null?"0":right.toString();
            switch ( this.op ){
            case MULTIPLY:  return "( " + l + " * " + r + " )";
            case DIVIDE:    return "( " + l + " / " + r + " )";
            case SUBTRACT:  return "( " + l + " - " + r + " )";
            default:        return "( " + l + " + " + r + " )";
            }
        }
    }
    public static ArrayList<MathNode> tokenize( final String sum )
    {
        int i = 0,
            p = 0;
        final int l = sum.length();
        final ArrayList<MathNode> tokens = new ArrayList<MathNode>();
        while ( i < l )
        {
            final SumNode sn;
            switch ( sum.charAt(i) ){
            case '*':   sn = new SumNode( Operation.MULTIPLY ); break;
            case '/':   sn = new SumNode( Operation.DIVIDE );       break;
            case '+':   sn = new SumNode( Operation.ADD );      break;
            case '-':   sn = new SumNode( Operation.SUBTRACT ); break;
            default:
                // TODO: Add something to check if number is valid
                ++i; 
                continue;
            }
            // TODO: Add something to check for zero-width numbers 
            final double value = Double.parseDouble( sum.substring( p, i ) );
            p = ++i;
            tokens.add( new ElementNode( value ) );
            tokens.add( sn );
        }
        // TODO: Add something to check for zero-width numbers
        final double value = Double.parseDouble( sum.substring( p ) );
        tokens.add( new ElementNode( value ) );
        return tokens;
    }
    public static void makeTree( final ArrayList<MathNode> tokens, final Operation op ){
        for ( int i = tokens.size() - 2; i >= 1; --i )
        {
            final MathNode node = tokens.get( i );
            if ( node.set( tokens.get(i-1), tokens.get(i+1), op) )
            {
                tokens.remove( i + 1 );
                tokens.remove( i - 1 );
                --i;
            }
        }
    }
    public static void main(final String[] args) {
        final String sum = "23.2-5.2*4.4/2.2+14";
        final ArrayList<MathNode> tokens = tokenize( sum );
        makeTree( tokens, Operation.DIVIDE );
        makeTree( tokens, Operation.MULTIPLY );
        makeTree( tokens, Operation.SUBTRACT );
        makeTree( tokens, Operation.ADD );
        final MathNode sum_tree = tokens.get(0);
        System.out.println( sum_tree + " = " + sum_tree.calc() );
    }
}
Sign up to request clarification or add additional context in comments.

5 Comments

Hey MTO, thanks for your answer. I've been playing with the code and was wondering what do the lines 'double l = left == null ? 0 : left.calc();' and 'double r = right == null ? 0 : right.calc();' actually do besides assign value to l and r?
That line is equivalent to: double l; if ( left == null ) { l = 0; } else { l = left.calc(); } (and it is the same for the right). The SumNodes (representing operations within the sum) form a binary tree (with ElementNodes at the leaves, representing the numbers within the sum) and all those lines do is see whether there is a child on that side of the tree - if so, it calculates the result of the sum for that sub-tree and, if not, returns zero. It is simply a calculation and does not do anything to modify the structure of the binary tree or change anything.
Okay I see. The only other problem Im having is understanding how the makeTree method works. Why do you need to have --i in both the for loop and if statement. Also how would you go about changing the code to see if has taken any brackets in the String to ensure they get priority over the rest of the expression. Thanks a lot, Ciaran.
Brackets are going to be difficult using that method (as its pretty basic) and you woull probably need to rewrite the tokenizer function to convert the sum from infix to postfix ordering and then generate the syntax tree from that. I've got 99% through a rewrite but am just trying to find the last few bugs and I'll post an edit when I'm done.
The two --i statements are because: the one in the for statement is used to iterate over the nodes; and the one in the if statement is used when the current node contains an operator which is being processed and then the immediately preceding and succeeding nodes are removed from the list (to be children of that current node within the binary tree) - in this case you don't want to process the immediately preceding node as it's already been removed so you need to decrement the counter an additional time.
1

The right way to do this would be to parse the input into an AST (abstract syntax tree). Then evaluation of the expressions is done in the right order.

The propensity to attempt to reduce all problems to tabular solutions is a huge anti pattern that, sadly, many programmers never learn. Not everything is a table. There are trees and graphs out there too.

Pretty much all parsing jobs should result in the construction of a tree (which may or may not be an implementation of the Composite Pattern).

1 Comment

What I really want to do is evaluate the value of the expression described in the input string and return the result. Could I not take these arrays put them on a stack and then work back of it as a reverse polish notation calculator?
0

Do you really need to scan all the operators, calculate priority, and re-order everything?

What if you just went though all the operators and values, and evaluated only the * and the /

Then go through again and evaluate the + and the -

Might be much easier than what you are trying to do.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.