Skip to content

Instantly share code, notes, and snippets.

@sfyn sfyn/Formula.java
Created Jan 23, 2014

Embed
What would you like to do?
Demonstrate a way to parse a mathematical
import java.util.LinkedList;
/**
* This class allows the calculation of a mathematical formula
*/
public class Formula {
private String formula;
private double result;
/**
* Constructor
*
* @param form The formula
*/
public Formula(String form) {
formula = form;
result = 0;
}
/**
* Get the result
*
* @return A double
*/
public double result() {
return result;
}
/**
* Helper to parse and push a number
*
* @param list List of numbers to push the number onto
* @param num A string representation of what we hope is a number
* @throws NumberFormatException
* @return An empty string to clear the num, unless there was a problem
*/
private String pushDouble(LinkedList<Double> list, String num) throws NumberFormatException {
//System.out.println("Try to parse: " + num + "; ");
list.add(Double.parseDouble(num));
return "";
}
/**
* Recursive parsing method
*
* @throws Exception
* @throws NumberFormatException
* @return The result of the operation
*/
public double evaluate() throws Exception, NumberFormatException {
int pos, brackets = 0, startNested = 0, endNested = 0;
byte pass;
String number = "";
LinkedList<Double> vals = new LinkedList<Double>();
LinkedList<Character> ops = new LinkedList<Character>();
// Main loop through the formula
for (pos=0; pos<formula.length(); pos++) {
char current = formula.charAt(pos);
Formula nested;
// First handle detection of sub clauses
switch (current) {
// Open a bracket
case '(':
if (number != "") number = pushDouble(vals, number);
if (brackets++ == 0)
startNested = pos+1;
//System.out.println("Open a bracket at position " + startNested);
break;
// Close a bracket
case ')':
if (brackets-- == 1)
endNested = pos;
//System.out.println("Close a bracket at position " + endNested);
break;
}
if (brackets == 0) { // Only process equations in the current level of nesting
switch (current) {
// Valid number characters
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '.':
number += current;
//System.out.println("Number is now: " + number);
break;
// Supported operations are stored for later use
case '+':
case '-':
if (number != "") number = pushDouble(vals, number);
//System.out.println("Add an operation " + current);
if (pos == 0) // Account for a leading sign
number += current;
else
ops.add(current);
break;
case '/':
case '*':
if (number != "") number = pushDouble(vals, number);
//System.out.println("Add an operation " + current);
ops.add(current);
break;
case ' ':
if (number != "") number = pushDouble(vals, number);
case '(':
case ')':
break;
default: // We have reached an unsupported character
throw new Exception("Unsupported character at position " + pos + " : " + current);
}
}
// Check if we've reached the outside of a nested formula and parse it
if (endNested != 0 && brackets == 0 && startNested != endNested) {
String clause = formula.substring(startNested, endNested);
nested = new Formula(clause);
nested.evaluate();
vals.add(nested.result());
startNested = endNested = 0;
//System.out.println("Evaluated clause " + clause + " = " + nested.result());
}
}
// Push any unparsed number onto the end of the vals
if (number != "")
number = pushDouble(vals, number);
// After the loop, check for error conditions
if (brackets != 0)
throw new Exception("Open sub-condition in " + formula);
// Pass through the values once for each order of operations
// each iteration will remove one of the operands and replace
// the other with a calculated value, or move forward
pass = 1;
pos = 0;
while (pass < 3 && ops.size() > 0) {
Expression exp = null;
// Move to next pass if necessary
if (pos >= ops.size()) {
pass++;
//System.out.println("Switch to pass " + pass);
pos = 0;
}
else {
// Try to construct an expression
//System.out.println(ops.size() + " ops left to do on pass " + pass + ", check position " + pos);
char op = ops.get(pos);
if (pass == 1 && (op == '*' || op == '/')) {
ops.remove(pos);
exp = new Expression(vals.get(pos), op, vals.remove(pos+1));
}
else if (pass == 2) {
if (!(op == '+' || op == '-'))
throw new Exception("Unsupported operation in final pass: " + op);
ops.remove(pos);
exp = new Expression(vals.get(pos), op, vals.remove(pos+1));
}
else { // Keep moving forward
pos++;
exp = null;
}
}
// Evaluate the expression if we can
if (exp != null && exp.eval())
vals.set(pos, exp.result);
else if (exp != null)
throw new Exception("Could not evaluate: " + exp.express());
}
result = vals.peek();
return result;
}
/**
* Helper to check if the value is '+', '-', '*' or '/'
*
* @param op The value to check
* @return True if the value is an operator, false otherwise
*/
private static boolean isOp(char op) {
boolean check;
switch (op) {
case '+':
case '-':
case '*':
case '/':
check = true;
break;
default:
check = false;
break;
}
return check;
}
/**
* Override toString
*/
public String toString() {
return formula + " = " + result;
}
/**
* Command-line functionality
*
* Will evaluate any formula provided as the first argument
*/
public static void main(String args[]) {
if (args.length > 0) {
Formula form = new Formula(args[0]);
try {
form.evaluate();
System.out.println(form);
}
catch (Exception e) {
System.out.println(e.getMessage());
}
}
else
System.out.println("Call this with\n\tjava Formula \"<A formula enclosed by quotes>\"");
}
/**
* Structures and evaluates expressions
*/
private class Expression {
private char op;
private double result, left, right;
/**
* Constructor
*
* @param lhs The left side of the expression
* @param op The operation linking lhs and rhs
* @param rhs The right side of the expression
*/
private Expression(double lhs, char operation, double rhs) {
left = lhs;
right = rhs;
op = operation;
}
/**
* String representation of the expression
*
* @return A string constructed of "left op right"
*/
private String express() {
return String.format("%.2f %c %.2f", left, op, right);
}
/**
* Try to evaluate the expression
*
* @return True if the expression was evaluated
*/
private boolean eval() {
//System.out.print("Evaluating " + express() + ": ");
if (isOp(op)) {
switch (op) {
case '+':
result = left + right;
break;
case '-':
result = left - right;
break;
case '*':
result = left * right;
break;
case '/':
result = left / right;
break;
}
//System.out.println(result);
return true;
}
else
return false;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.