Calculator Enactment

Prefix Notation Visualization

import java.util.function.BiFunction;

/**
 * Token class
 * A Token is the key component of a mathematical expression
 * - Operators.  define the operator token, precedence, and mathematical calculation
 * - Parenthesis.  used to group terms
 */
public class Token {
    private final Character token;
    private final int precedence;
    private final BiFunction<Double, Double, Double> calculation;
    private final int numArgs;

    // Constructor for passive token, used for non-operator tokens
    public Token() {
        this('0');  // telescope to next constructor
    }

    // Constructor for simple token, used for parenthesis
    public Token(Character token) {
        this(token,0,null,0); // telescope to next constructor
    }

    // Constructor for operators, contains precedence and calculation method
    public Token(Character token, int precedence,  BiFunction<Double, Double, Double> calculation, int numArgs) {
        this.token = token;
        this.precedence = precedence;
        this.calculation = calculation;
        this.numArgs = numArgs;
    }

    // Getter for token
    public Character getToken() {
        return token;
    }

    // Getter for precedence
    public int getPrecedence() {
        return precedence;
    }

    public int getNumArgs() {
        return numArgs;
    }

    // Is precedent calculation
    public boolean isPrecedent(Token token) {
        return this.precedence > token.getPrecedence();
    }

    // Math calculation for operator and operands
    public Double calculate(Double operand1, Double operand2) {
        return this.calculation.apply(operand1, operand2);
    }

    // String return for token / operator
    public String toString() {
        return this.token.toString();
    }
    
}
import java.util.function.BiFunction;

/**
 * TermOrOperator class is a subclass of Token
 * TermOrOperator is used to define the parts of a mathematical expression
 * - Values.  a string representation of the numerical value in the expression
 * - Operators.  define the operator token, precedence, and mathematical calculation
 * - Parenthesis.  used to group terms
 */
public class TermOrOperator extends Token {
    private final String value;

    // Constructor for values
    public TermOrOperator(String value) {
        this.value = value;
    }

    // Constructor for parenthesis
    public TermOrOperator(Character token) {
        super(token);
        this.value = null;
    } 

    // Constructor for operators
    public TermOrOperator(Character token, int precedence, BiFunction<Double, Double, Double> calculation) {
        super(token, precedence, calculation, 2);
        this.value = null;
    }

    // Constructor for operators
    public TermOrOperator(Character token, int precedence, BiFunction<Double, Double, Double> calculation, int numArgs) {
        super(token, precedence, calculation, numArgs);
        this.value = null;
    }

    // Get method for retrieving value
    public String getValue() {
        return value;
    }

    // toString method to return value according to type: value, operator, or parenthesis
    public String toString() {
        if (this.value == null) {
            return super.toString();
        }
        return this.value;
    }   
}
import java.util.Map;
import java.util.function.BiFunction;
import java.util.HashMap;

/**
 * Terms class is a collection of Term objects
 * Terms are used to define the parts of a mathematical expression
 * - Operators.  define the operator, precedence, and mathematical calculation
 * - Parenthesis.  used to seperate and group terms
 * - Space. Used to seperate terms
 * 
 * A Map is choosen as the data structure because it enables fast lookups of Terms
 */
public class Tokens {
    // Terms are stored in map, using Term token as the key
    private Map<Character, TermOrOperator> map;

    // Constructor initializes map
    public Tokens() {
        this.map = new HashMap<>();
    }

    // Put method for adding Parenthesis and Space
    public void put(Character token) {
        this.map.put(token, new TermOrOperator(token));
    }

    // Put method for adding Operators, precedence, and calculation
    public void put(Character token, int precedence, BiFunction<Double, Double, Double> calculation, int numArgs) {
        this.map.put(token, new TermOrOperator(token, precedence, calculation, numArgs));
    }

    // Put method for adding Operators, precedence, and calculation
    public void put(Character token, int precedence, BiFunction<Double, Double, Double> calculation) {
        this.map.put(token, new TermOrOperator(token, precedence, calculation));
    }

    // Get method for retrieving Term based on token lookup  
    public TermOrOperator get(Character token) {
        return this.map.get(token);
    }

    // Get precedence method for retrieving precedence based on token lookup
    public int getPrecedence(Character token) {
        return this.map.get(token).getPrecedence();
    }

    // Contains method for checking if token exists in map
    public boolean contains(Character token) {
        return this.map.containsKey(token);
    }

    // toString method for converting entire map to string
    public String toString() {
        return this.map.toString();
    }

}

import java.util.ArrayList;
import java.util.Stack;

/** In mathematics,
    an expression or mathematical expression is a finite combination of symbols that is well-formed
    according to rules that depend on the context.
   In computers,
    expression can be hard to calculate with precedence rules and user input errors
    to handle computer math we often convert strings into reverse polish notation
*/
public class Calculator {
    // Key instance variables
    private final String expression;
    private ArrayList<TermOrOperator> terms = new ArrayList<>();
    private ArrayList<TermOrOperator> rpnTerms = new ArrayList<>();
    private Tokens operators = new Tokens();
    private Tokens seperators = new Tokens();
    private Double result = 0.0;

    /**
     * Calculator constructor to create a calculation object for an expression
     * This calculates the expression and saves the result and intermediate steps in instance variables
     * 
     * @param expression
     */
    public Calculator(String expression) {   
        // set up tokens used in an calculator
        initOperators();
        initSeperators();

        // original expression
        this.expression = expression;

        // parse expression into individual terms
        this.termTokenizer();

        // place terms into polish notation
        this.termsToPrefix();

        // calculate polish notation expression into a result
        this.prefixToResult();
    }

    /** 
     * Method to initialize operators, precedence, and calculation
     * Fundamental to data structures is the ability to store and retrieve data quickly
     * In this case, we are using a map to store operators and their precedence and calculation methods.
     * 
     * Note, that through overloaded methods, we can store operators as Terms with different numbers of arguments
     * This is specifically useful, in this case, for unary operators like square root
     */
    private void initOperators() {
        // Operators contain a token, precedence, and calculation using BiFunction
        operators.put('*', 3, (a, b) -> a * b);
        operators.put('/', 3, (a, b) -> a / b);
        operators.put('%', 3, (a, b) -> a % b);
        operators.put('+', 4, (a, b) -> a + b);
        operators.put('-', 4, (a, b) -> a - b);
        operators.put('^', 2, (a, b) -> Math.pow(a, b)); // Power operation
        operators.put('√', 1, (a, b) -> Math.sqrt(a), 1); // Square root operation
    }

    /**
     * Method to initialize seperators
     * Seperators are used to seperate terms 
     * Additionally, the parenthesis are used to group terms and operations
     *
     * Note, that through overloaded methods, we can store seperators as Terms with different numbers of arguments 
     */
    private void initSeperators() {
        // Seperators contain a token 
        seperators.put(' ');
        seperators.put('(');
        seperators.put(')');
    }

    /**
     * Term Tokenizer takes original expression and converts it to ArrayList of mathematical terms and values
     * Populates the this.terms instance of type ArrayList<TermOrOperator>
     * In essence, this method tokenizes the expression into individual terms/cells in the ArrayList 
     */ 
    private void termTokenizer() {
        int start = 0;  // term split starting index
        StringBuilder multiCharTerm = new StringBuilder();    // term holder
        for (int i = 0; i < this.expression.length(); i++) {
            Character c = this.expression.charAt(i);
            
            if ( operators.contains(c) || seperators.contains(c)  ) {
                // 1st check for working term and add if it exists
                if (multiCharTerm.length() > 0) {
                    this.terms.add(new TermOrOperator(this.expression.substring(start, i)));
                }
                // Add operator or parenthesis term to list
                TermOrOperator t = operators.get(c);
                if (t == null) {
                    t = seperators.get(c);
                }
                if (t != null && t.getToken() != ' ') {
                    this.terms.add(t);
                }

                // Get ready for next term
                start = i + 1;
                multiCharTerm = new StringBuilder();
            } else {
                // multi character terms: numbers, functions, perhaps non-supported elements
                // Add next character to working term
                multiCharTerm.append(c);
            }

        }
        // Add last term
        if (multiCharTerm.length() > 0) {
            this.terms.add(new TermOrOperator(this.expression.substring(start)));
        }
    }

    /**
     * This method populates the this.rpnTerms instance of type ArrayList<TermOrOperator> from the this.terms
     * Observe the inorder shift from before (terms) to after (termsToRPN) reorder
     * This reordering is called Reverse Polish Notation (RPN)
     * The terms are reordered by parenthesis and operator precedence, also called postfix notation
     * RPN is commonly used in computer science to evaluate mathematical expressions
     * RPN originated with with the Polish mathematician Jan Łukasiewicz
     * RPN was later popularized by the Hewlett-Packard company in the 1970s with their scientific calculators
     * 
     */ 
    private void termsToPrefix() {
        // A stack is used to hold operators in the proper order for prefix notation
        Stack<TermOrOperator> operatorStack = new Stack<>();
    
        // Process each term (similar to termsToRPN but reversed)
        for (TermOrOperator term : terms) {
            if (term.getToken() == ')') { // close parenthesis
                // Same logic as RPN, but reversed
                while (operatorStack.peek() != null && operatorStack.peek().getToken() != '(') {
                    rpnTerms.add(0, operatorStack.pop()); // Push to the front for prefix order
                }
                operatorStack.pop(); // remove open parenthesis
            } else if (term.getToken() == '(') { // open parenthesis
                operatorStack.push(term);
            } else if (operators.contains(term.getToken())) { // operator
                // For prefix, we compare operator precedence and push onto stack accordingly
                while (!operatorStack.isEmpty() && operators.contains(operatorStack.peek().getToken()) &&
                       term.isPrecedent(operatorStack.peek())) {
                    rpnTerms.add(0, operatorStack.pop());
                }
                operatorStack.push(term);
            } else { // operand
                rpnTerms.add(0, term); // Directly add operands to the front
            }
        }
        // Empty the operator stack to the front for prefix order
        while (!operatorStack.isEmpty()) {
            rpnTerms.add(0, operatorStack.pop());
        }
    }    

    /**
     * The method takes this.rpnTerms and calculates them into a final result
     * Values proceed the operators, value(s) are popped from the this.rpmTerms and pushed onto the calcStack
     * Operators are applied to the value(s) in the calcStack and the result is pushed back onto the calcStack 
     * The final result is the remaining value on the calcStack with the this.rpnTerms is empty 
     * The final result is stored in this.result 
     * 
     */ 
    private void prefixToResult() {
        // Stack for operands
        Stack<Double> calcStack = new Stack<>();
    
        // Process the prefix expression from right to left
        for (int i = rpnTerms.size() - 1; i >= 0; i--) {
            TermOrOperator term = rpnTerms.get(i);
            Double operand1 = 0.0, operand2 = 0.0, result = 0.0;
    
            if (operators.contains(term.getToken())) {
                if (term.getNumArgs() == 1) { // Unary operator
                    operand1 = calcStack.pop();
                    result = term.calculate(operand1, 0.0); // Pass a dummy 0.0 for second operand
                } else { // Binary operator
                    operand1 = calcStack.pop();
                    operand2 = calcStack.pop();
                    result = term.calculate(operand1, operand2);
                }
                calcStack.push(result);
            } else {
                // Operand, push onto stack
                calcStack.push(Double.parseDouble(term.toString()));
            }
        }
    
        // Final result will be the last value on the stack
        this.result = calcStack.pop();
    }    

    // Print the expression, terms, and result from the instance variables
    public String toString() {
        return ("Original expression: " + this.expression + "\n" +
                "Tokenized expression: " + this.terms.toString() + "\n" +
                "Polish Notation: " +this.rpnTerms.toString() + "\n" +
                "Final result: " + String.format("%.2f", this.result));
    }

    // Tester method contains a few examples of mathematical expressions
    public static void main(String[] args) {
        Calculator simpleMath = new Calculator("100 + 200  * 3");
        System.out.println("Simple Math\n" + simpleMath);

        System.out.println();

        Calculator parenthesisMath = new Calculator("(100 + 200)  * 3");
        System.out.println("Parenthesis Math\n" + parenthesisMath);

        System.out.println();

        Calculator decimalMath = new Calculator("100.2 - 99.3");
        System.out.println("Decimal Math\n" + decimalMath);

        System.out.println();

        Calculator moduloMath = new Calculator("300 % 200");
        System.out.println("Modulo Math\n" + moduloMath);

        System.out.println();

        Calculator divisionMath = new Calculator("300/200");
        System.out.println("Division Math\n" + divisionMath);

        System.out.println();

        Calculator pythagoreanMath = new Calculator("√(3^2 + 4^2)");
        System.out.println("Pythagorean Theorem\n" + pythagoreanMath);

    }
}
Calculator.main(null);
Simple Math
Original expression: 100 + 200  * 3
Tokenized expression: [100, +, 200, *, 3]
Polish Notation: [+, *, 3, 200, 100]
Final result: 700.00

Parenthesis Math
Original expression: (100 + 200)  * 3
Tokenized expression: [(, 100, +, 200, ), *, 3]
Polish Notation: [*, 3, +, 200, 100]
Final result: 900.00

Decimal Math
Original expression: 100.2 - 99.3
Tokenized expression: [100.2, -, 99.3]
Polish Notation: [-, 99.3, 100.2]
Final result: -0.90

Modulo Math
Original expression: 300 % 200
Tokenized expression: [300, %, 200]
Polish Notation: [%, 200, 300]
Final result: 200.00

Division Math
Original expression: 300/200
Tokenized expression: [300, /, 200]
Polish Notation: [/, 200, 300]
Final result: 0.67

Pythagorean Theorem
Original expression: √(3^2 + 4^2)
Tokenized expression: [√, (, 3, ^, 2, +, 4, ^, 2, )]
Polish Notation: [√, +, ^, 2, 4, ^, 2, 3]
Final result: 4.90

Comments

You are seeing this because your Disqus shortname is not properly set. To configure Disqus, you should edit your _config.yml to include either a disqus.shortname variable.

If you do not wish to use Disqus, override the comments.html partial for this theme.