* The utility class for expression evaluation and generation.<br>
* The generator can be instantiated with different configurable values, allowing for flexible expression generation.<br>
* The evaluator can also be statically accessed in this class, since it does not have configurable values.
* <p>
* <i>Intended for use in the main arithmetic trainer, but third-party programs may also utilize this class as an expression generation and evaluation library if needed.</i>
* @author despawningbone
*/
publicclassExpressionGenerator{
/**
* A weighted representation of the operators' weighted chance to appear in generation.
* <p>
* <b>Note: This is a universal value across all generators!</b>
* Generates a direct {@link ExpressionGenerator#stepEval(List)} parsable expression along with the steps needed to solve according to the generator object's specifications. <br>
* Note that as real number operations can be extremely difficult to calculate mentally, the arithmetic trainer will only generate integers.
*
* @return a generated expression string
*/
publicList<String>generate(){//generates only integers
ThreadLocalRandomran=ThreadLocalRandom.current();
intstartingValue=ran.nextInt(origin,bound);//start it with a value so it wont be invalid
Stringexp=String.valueOf(startingValue);
for(inti=0;i<operatorCount;i++){
intopnd=ran.nextInt(origin,bound);
Stringoperator=operators.floorEntry(ran.nextInt(100)).getValue();//choose one randomly
if(ran.nextBoolean()){//randomly choose to put in front or behind the old expression
List<String>steps=stepEval(newArrayList<String>(Arrays.asList(exp)));//validate if whole expression is valid; might induce performance overhead but its the quickest way to do so
//no need evaluateWithSteps() coz the generated expression format can be directly parsed without reformatting
returnsteps;
}catch(IllegalArgumentExceptione){
returngenerate();//generate a new one until theres no divide by zero anymore
}
}
/**
* Compares the current expression generator with another and determines if the current generator has strictly more difficult specifications than the other.<br>
* The comparing criteria is as follows:
* <ul>
* <li>Higher bracket chance</li>
* <li>Higher operator count</li>
* <li>Larger numeric range</li>
* </ul><p>
* All of the above must be true in order for the current generator to be considered harder.<br>
* @param gen the generator to be compared with
* @return true if the current generator is harder, false otherwise
* Lints and evaluates a string expression, and returns the evaluated result with steps.<br>
*
* @param exp the expression to be evaluated
* @return an array storing the steps involved in evaluating the result
*/
publicstaticList<String>evaluateWithSteps(Stringexp){//wrapper for the stepEval function to make it easier to understand externally, and also acts as an expression linter
if(exp==null||Pattern.compile("[^+\\-*/%().0-9E ]").matcher(exp).find()){//checking for anything other than the operators supported
Patternpattern=Pattern.compile("\\(([0-9.E]*)\\)|\\((-*[0-9.E]*)\\)");//remove unnecessary brackets, eg 1-(-6) -> 1--6 so splitting later on will not break
exp=exp.replaceAll("\\s","")//DONE remove all spaces
.replaceAll("\\)([0-9(])",")*$1").replaceAll("([0-9)])\\(","$1*(");//add back *s between brackets so it wont break, but the steps will include it
Matchermatch=pattern.matcher(exp);
while(match.find()){//to remove multiple brackets if present, eg ((x))
.replaceAll("(?<![0-9)])[+]+","")//remove + for positive indicators
.replaceAll("(?<![0-9)])[-]((-{4})*)-",""))));// make consecutive negative signs become one, excluding x--y because it can be x-(-x)
}
/**
* An internal recursive function that evaluates the last expression in the given list step by step until a single numeric value is yielded.<br>
* <p>
* This method is not expected to be used alone! As a result, it has the private modifier to prevent outside access.<br>
* It is strongly recommended that third-party programs should utilize {@link ExpressionGenerator#evaluateWithSteps(String)} instead of accessing this method through reflection.<br>
* @param steps a list containing the expression(s)
* @return a list containing the expressions including the newly evaluated one
if(newestExp.matches("-{0,1}[0-9.]*(?:E-{0,1})?[0-9.]*"))returnsteps;//stopping anchor; only case where - sign should exist is at the first char or after E for negative
String[]splits=newestExp.split("(?<=[^0-9.E])(?=(?<![^0-9]-))(?=(?<!^-))|(?=[^0-9.E])(?<=[^E])");//split with delimiter, aka expressions; second group defines - without preceding number as negative indicator instead of operator, and 3rd group checks the start of line; last group escapes negative E notations (e.g. E-2)
if(split.matches("[^0-9.E]*")){//if its an operand we dont need to do anything
if(split.equals("(")){
isPrior=false;//because parentheses preceed any operators, so remove other priorities
sameLevel=false;
}elseif(!isPrior&&(split.equals("*")||split.equals("/")||split.equals("%"))){//only 3 with elevated priorities
isPrior=true;
index=i;
}elseif(!isPrior&&(!sameLevel||!(splits[index].equals("+")||splits[index].equals("-")))&&(split.equals("+")||split.equals("-"))){//as its not prioritized, only when there is no prioritized operator will it be evaluated; if it is in the same bracket, evaluate the first operator
index=i;
sameLevel=true;
}elseif(split.equals(")")){
isPrior=true;//because parentheses are of highest priority
}
}
}
/*
* start eval
*/
Stringop=splits[index];
try{
doublex=Double.parseDouble(splits[index-1]);
doubley=Double.parseDouble(splits[index+1]);
doubleval=0;
switch(op){
case"+":val=x+y;
break;
case"-":val=x-y;
break;
case"*":val=x*y;
break;
case"/":val=x/y;
break;
case"%":val=x%y;//apparently in java its remainder, not modulus
break;
}
splits[index-1]="";//wipe the old values
splits[index+1]="";
splits[index]=String.valueOf(val);//put the new value in
thrownewIllegalArgumentException("Invalid expression!");//this should only throw if there is 2 operators at the same place, result reaches infinity or undefined, or if there is unfinished operators at the ends