Skip to content

Instantly share code, notes, and snippets.

@Tran-Antoine
Last active February 4, 2019 20:58
Show Gist options
  • Save Tran-Antoine/60ecbe786123a1d6cca7ff9f8aa32aa3 to your computer and use it in GitHub Desktop.
Save Tran-Antoine/60ecbe786123a1d6cca7ff9f8aa32aa3 to your computer and use it in GitHub Desktop.
Mathematical expression manager
package net.akami.mask.core;
import net.akami.mask.utils.ReducerFactory;
public class MainTester {
public static void main(String... args) {
RestCalculation rest = ReducerFactory.reduce("4*3^2-12");
System.out.println(rest.asInt());
MaskExpression expression = new MaskExpression("6x^2 -3y +5");
System.out.println(expression.imageFor(2, 3.5f).asFloat());
/*
Output:
24
18.5
*/
}
}
package net.akami.mask.core;
import net.akami.mask.utils.ReducerFactory;
import java.util.ArrayList;
import java.util.List;
public class MaskExpression {
private static final String NON_VARIABLES = "0123456789+-*=^";
private final String expression;
private final List<Character> variables;
public MaskExpression(final String expression) {
this.expression = expression.replaceAll("\\s", "");
this.variables = createVariables();
}
private List<Character> createVariables() {
List<Character> variables = new ArrayList<>();
for(int i = 0; i < expression.length(); i++) {
char c = expression.charAt(i);
if(!NON_VARIABLES.contains(String.valueOf(c)) && !variables.contains(c)) {
variables.add(c);
}
}
return variables;
}
public RestCalculation imageFor(float... values) {
String replaced = expression;
for(int i = 0; i < values.length; i++) {
char var = variables.get(i);
replaced = replace(var, values[i], replaced);
}
return ReducerFactory.reduce(replaced);
}
private String replace(char var, float value, String self) {
StringBuilder builder = new StringBuilder();
for(int i = 0; i < self.length(); i++) {
if(self.charAt(i) == var && i!= 0) {
if(NON_VARIABLES.contains(String.valueOf(self.charAt(i-1)))) {
//the char before the variable is a number. 4x obviously means 4*x
builder.append("*"+value);
} else {
// No number before the variable, for instance 3+x
builder.append(value);
}
} else {
// No variable found, we just add the same char
builder.append(self.charAt(i));
}
}
return builder.toString();
}
public int getVariablesAmount() {
return variables.size();
}
}
package net.akami.mask;
import net.akami.mask.utils.MathUtils;
public enum Operation {
SUM('+', MathUtils::sum),
SUBTRACT('-', MathUtils::subtract),
MULT('*', MathUtils::mult),
DIVIDE('/', MathUtils::divide),
POW('^', MathUtils::pow),
NONE(' ', null);
private char sign;
private MathOperation function;
Operation(char sign, MathOperation function) {
this.sign = sign;
this.function = function;
}
public char getSign() {
return sign;
}
public float compute(String a, String b) {
return function.compute(Float.parseFloat(a), Float.parseFloat(b));
}
public static Operation getBySign(char sign) {
for(Operation operation : values()) {
if(operation.sign == sign) {
return operation;
}
}
return null;
}
private interface MathOperation {
float compute(float a, float b);
}
}
package net.akami.mask.utils;
import java.util.List;
import net.akami.mask.Operation;
import net.akami.mask.core.RestCalculation;
import net.akami.mask.core.Tree;
import net.akami.mask.core.Tree.Branch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ReducerFactory {
private static final Logger LOGGER = LoggerFactory.getLogger(ReducerFactory.class.getName());
private static final Operation[] OPERATIONS;
static {
OPERATIONS = new Operation[]{
Operation.SUM, Operation.SUBTRACT,
Operation.MULT, Operation.DIVIDE,
Operation.POW, Operation.NONE
};
}
public static RestCalculation reduce(String exp) {
long time = System.nanoTime();
Tree tree = new Tree();
// deletes all the spaces
String localExp = exp.replaceAll("\\s", "");
tree.new Branch(localExp);
// split the expression for each pair of signs.
for(int i = 0; i < OPERATIONS.length; i+=2) {
splitBy(tree, OPERATIONS[i].getSign(), OPERATIONS[i+1].getSign());
}
RestCalculation result;
try {
result = TreeUtils.mergeBranches(tree);
} catch (ArithmeticException | NumberFormatException e) {
if(e instanceof ArithmeticException)
LOGGER.error("Non solvable mathematical expression given : {}", exp);
// NumberFormatException has not been handled yet, therefore the next instruction won't
// ever be called
else
LOGGER.error("Wrong format in the expression {}", exp);
result = new RestCalculation("undefined");
}
float deltaTime = (System.nanoTime() - time) / 1000000f;
LOGGER.info("Expression successfully reduced in {} seconds.", deltaTime);
return result;
}
private static void splitBy(Tree tree, char c1, char c2) {
List<Branch> branches = tree.getBranches();
int index = 0;
while(true) {
Branch actual = branches.get(index);
String exp = actual.getExpression();
/*
We must go from the end to the beginning. Otherwise, operations' priority is not respected.
For instance, 2/2*2 = 1. If we go from 0 to exp.length() -1, the expression will be divided like this :
2 |/| 2*2. 2*2 will be calculated first, the final result will be 1/2. If we go from exp.length() -1
to 0, the expression will be divided like this :
2 / 2 |*| 2. 2/2 will be calculated first, the final result will be 2.
*/
for(int i = exp.length()-1; i >= 0; i--) {
/*
Avoids splits with the roots parts. For instance, when splitting by '+' and '-' for the
expression '5+3*2', the result will be 5+3*2, 5, and 3*2. When splitting by '*' and '/', we don't
want '5+3*2' to be split, because it contains one of the previous operations (+).
*/
if(actual.hasChildren())
break;
char c = exp.charAt(i);
if(c == c1 || c == c2) {
TreeUtils.createNewBranch(tree, actual, exp, i, c);
break;
}
}
if(index + 1 < branches.size()) {
index++;
} else {
break;
}
}
LOGGER.debug("Separations with the operations {} and {} are now done", c1, c2);
}
}
package net.akami.mask.core;
public class RestCalculation {
private final String rest;
public RestCalculation(String rest) {
this.rest = rest;
}
public String asExpression() {
return rest;
}
public int asInt() {
return (int) Float.parseFloat(rest);
}
public float asFloat() {
return Float.parseFloat(rest);
}
public MaskExpression asMaskExpression() {
return new MaskExpression(rest);
}
}
package net.akami.mask.core;
import java.util.ArrayList;
import java.util.List;
public class Tree {
private List<Branch> branches;
public Tree() {
this.branches = new ArrayList<>();
}
public List<Branch> getBranches() { return branches; }
public class Branch {
private Branch left;
private Branch right;
private char operation;
private String expression;
private boolean reduced;
private float reducedValue;
public Branch(String expression) {
this.expression = expression;
reduced = false;
branches.add(this);
}
public Branch getLeft() { return left; }
public Branch getRight() { return right; }
public char getOperation() { return operation; }
public String getExpression() { return expression; }
public float getReducedValue() { return reducedValue; }
public boolean isReduced() { return reduced; }
public void setOperation(char operation) { this.operation = operation; }
public void setLeft(Branch left) { this.left = left; }
public void setRight(Branch right) { this.right = right; }
public void setReducedValue(float value) {
this.reducedValue = value;
reduced = true;
}
@Override
public boolean equals(Object obj) {
if(obj instanceof Branch) {
return expression.equals(((Branch) obj).expression);
}
return false;
}
public boolean hasChildren() {
return left != null || right != null;
}
public boolean canBeCalculated() {
if(!hasChildren()) {
return false;
}
return !(left.hasChildren() || right.hasChildren());
}
}
}
package net.akami.mask.utils;
import net.akami.mask.Operation;
import net.akami.mask.core.RestCalculation;
import net.akami.mask.core.Tree;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.akami.mask.core.Tree.Branch;
public class TreeUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(TreeUtils.class.getName());
public static RestCalculation mergeBranches(Tree tree) {
/*
If we give a very simple expression such as '50' to the reducer, it will detect that no operation
needs to be done, and will simply calculate nothing. In this case, we return the expression itself.
*/
if(tree.getBranches().size() == 1) {
return new RestCalculation(tree.getBranches().get(0).getExpression());
}
/*
Merging the branches from the last one to the first one (this initial expression)
*/
for (int i = tree.getBranches().size() -1; i >= 0; i--) {
Branch branch = tree.getBranches().get(i);
LOGGER.debug("Actual branch : {}\n", branch.getExpression());
if(!branch.canBeCalculated()) {
LOGGER.debug("Not calculable.");
continue;
}
// We are sure that the branch has a left and a right part, because the branch can be calculated
String left = branch.getLeft().getExpression();
String right = branch.getRight().getExpression();
// We need to check whether a reduced form has been calculated or not
if(branch.getLeft().isReduced()) {
left = String.valueOf(branch.getLeft().getReducedValue());
}
if(branch.getRight().isReduced()) {
right = String.valueOf(branch.getRight().getReducedValue());
}
float value = Operation.getBySign(branch.getOperation()).compute(left, right);
// The result is defined as the reduced value of the expression
LOGGER.debug("Successfully calculated the value of "+branch.getExpression()+" : "+value);
branch.setReducedValue(value);
branch.setLeft(null);
branch.setRight(null);
Branch first = tree.getBranches().get(0);
if(first.isReduced()) {
if(String.valueOf(first.getReducedValue()).equals("Infinity"))
throw new ArithmeticException();
return new RestCalculation(String.valueOf(first.getReducedValue()));
}
}
return null;
}
public static void createNewBranch(Tree tree, Branch actual, String exp, int index, char operation) {
String left = exp.substring(0, index);
String right = exp.substring(index+1);
actual.setLeft(tree.new Branch(left));
actual.setRight(tree.new Branch(right));
actual.setOperation(operation);
}
}
@Tran-Antoine
Copy link
Author

Dans un deuxième temps je ferai en sorte que ReducerFactory#reduce(String) supporte les parenthèses et les variables, mais voilà pour l'instant on peut uniquement donner les 5 premières opérations (+, -, *, /, ^). Pour faire une racine carrée, utilisez donc x^0.5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment