Created
January 23, 2014 14:20
-
-
Save leylaso/8579202 to your computer and use it in GitHub Desktop.
Demonstrate a way to parse a mathematical
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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