Calculator Front End
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
import java.util.Scanner;
/* 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
to handle errors we perform try / catch or set default conditions to trap errors
*/
public class Calculator {
// Key instance variables
private final String expression;
private ArrayList<String> tokens;
private ArrayList<String> reverse_polish;
private Double result = 0.0;
public boolean test_continue;
// Helper definition for supported operators
private final Map<String, Integer> OPERATORS = new HashMap<>();
{
// Map<"token", precedence>
OPERATORS.put("^", 2);
OPERATORS.put("R", 2);
OPERATORS.put("*", 3);
OPERATORS.put("/", 3);
OPERATORS.put("%", 3);
OPERATORS.put("+", 4);
OPERATORS.put("-", 4);
}
// Helper definition for supported operators
private final Map<String, Integer> SEPARATORS = new HashMap<>();
{
// Map<"separator", not_used>
SEPARATORS.put(" ", 0);
SEPARATORS.put("(", 0);
SEPARATORS.put(")", 0);
}
// Create a 1 argument constructor expecting a mathematical expression
public Calculator(String expression) {
// original input
this.expression = expression;
System.out.println(expression);
// parse expression into terms
this.termTokenizer();
// place terms into reverse polish notation
this.tokensToReversePolishNotation();
// calculate reverse polish notation
if (this.tokensToReversePolishNotation()){
this.rpnToResult();
}
else{
System.out.println("error with paranthesis");
}
}
// Test if token is an operator
private boolean isOperator(String token) {
// find the token in the hash map
return OPERATORS.containsKey(token);
}
// Test if token is an separator
private boolean isSeparator(String token) {
// find the token in the hash map
return SEPARATORS.containsKey(token);
}
// Compare precedence of operators.
private Boolean isPrecedent(String token1, String token2) {
// token 1 is precedent if it is greater than token 2
return (OPERATORS.get(token1) - OPERATORS.get(token2) >= 0) ;
}
// Term Tokenizer takes original expression and converts it to ArrayList of tokens
private void termTokenizer() {
// contains final list of tokens
this.tokens = new ArrayList<>();
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 ( isOperator(c.toString() ) || isSeparator(c.toString()) ) {
// 1st check for working term and add if it exists
if (multiCharTerm.length() > 0) {
tokens.add(this.expression.substring(start, i));
}
// Add operator or parenthesis term to list
if (c != ' ') {
tokens.add(c.toString());
}
// 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) {
tokens.add(this.expression.substring(start));
}
System.out.println(tokens);
}
public boolean valid_paranthesis(){
int open_para_counter = 0;
int close_para_counter = 0;
ArrayList<String> tokens_strings = tokens;
for (int i =0; i < tokens_strings.size(); i++){
String variable = tokens_strings.get(i);
if (variable.equals("(") || variable.equals(" (")){
open_para_counter = open_para_counter +1;
}
if (variable.equals(")") || variable.equals(" )")){
close_para_counter = close_para_counter +1;
}
}
if (open_para_counter == close_para_counter){
return true;
}
return false;
}
// Takes tokens and converts to Reverse Polish Notation (RPN), this is one where the operator follows its operands.
private boolean tokensToReversePolishNotation () {
// contains final list of tokens in RPN
this.reverse_polish = new ArrayList<>();
if (valid_paranthesis()){
// stack is used to reorder for appropriate grouping and precedence
Stack<String> tokenStack = new Stack<String>();
for (String token : tokens) {
switch (token) {
// If left bracket push token on to stack
case "(":
tokenStack.push(token);
break;
case ")":
while (tokenStack.peek() != null && !tokenStack.peek().equals("("))
{
reverse_polish.add( tokenStack.pop() );
}
tokenStack.pop();
break;
case "+":
case "-":
case "*":
case "/":
case "%":
case "^":
case "R":
// While stack
// not empty AND stack top element
// and is an operator
while (tokenStack.size() > 0 && isOperator(tokenStack.peek()))
{
if ( isPrecedent(token, tokenStack.peek() )) {
reverse_polish.add(tokenStack.pop());
continue;
}
break;
}
// Push the new operator on the stack
tokenStack.push(token);
break;
default: // Default should be a number, there could be test here
this.reverse_polish.add(token);
}
}
// Empty remaining tokens
while (tokenStack.size() > 0) {
reverse_polish.add(tokenStack.pop());
}
test_continue = true;
return test_continue;
}
else{
return test_continue;
}
}
// Takes RPN and produces a final result
private void rpnToResult()
{
// stack is used to hold operands and each calculation
Stack<Double> calcStack = new Stack<Double>();
// RPN is processed, ultimately calcStack has final result
for (String token : this.reverse_polish)
{
// If the token is an operator, calculate
if (isOperator(token))
{
// Pop the two top entries
Double entry1 = calcStack.pop();
Double entry2 = calcStack.pop();
// Calculate intermediate results
if(token.equals("+"))
{
result = entry1 + entry2;
}
else if(token.equals("-")){
result = entry1 - entry2;
}
else if(token.equals("*")){
result = entry1 * entry2;
}
else if(token.equals("/")){
result = entry2 / entry1;
}
else if(token.equals("%")){
result = entry2 % entry1;
}
else if(token.equals("^")){
result = Math.pow(entry2, entry1);
}
else if(token.equals("R")){
result = Math.pow(entry2, (1/ entry1));
}
// Push intermediate result back onto the stack
calcStack.push( result );
}
// else the token is a number push it onto the stack
else
{
calcStack.push(Double.valueOf(token));
}
}
// Pop final result and set as final result for expression
this.result = calcStack.pop();
}
// Print the expression, terms, and result
public String toString() {
return ("Original expression: " + this.expression + "\n" +
"Tokenized expression: " + this.tokens.toString() + "\n" +
"Reverse Polish Notation: " +this.reverse_polish.toString() + "\n" +
"Final result: " + String.format("%.2f", this.result));
}
public String jsonify() {
String json = "{ \"Expression\": \"" + this.expression + "\", \"Tokenized Expression\": \"" + this.tokens +
"\", \"Reverse Polish Notation\": \"" + this.reverse_polish + "\", \"Result\": " + this.result + " }";
return json;
}
// Tester method
public static void main(String[] args) {
// Random set of test cases
// Random set of test cases
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 unfinished_paranthesis = new Calculator("(27/3");
System.out.println("Division Math\n" + unfinished_paranthesis);
System.out.println();
Calculator exponent = new Calculator("2^8");
System.out.println("Exponent Math\n" + exponent);
System.out.println();
Calculator sqrt = new Calculator("125R3");
System.out.println("Exponent Math\n" + sqrt);
System.out.println();
Scanner myObj = new Scanner(System.in); // Create a Scanner object
String expression_user = myObj.nextLine(); // Read user input
Calculator input = new Calculator(expression_user);
System.out.println("Exponent Math\n" + input);
System.out.println();
}
}
Calculator.main(null);