package miscellaneous;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lipstone.joshua.parser.Tokenizer;
import lipstone.joshua.parser.exceptions.InvalidOperationException;
import lipstone.joshua.parser.exceptions.ParserException;
import lipstone.joshua.parser.exceptions.PluginConflictException;
import lipstone.joshua.parser.exceptions.UndefinedResultException;
import lipstone.joshua.parser.plugin.ParserPlugin;
import lipstone.joshua.parser.plugin.helpdata.Operation;
import lipstone.joshua.parser.plugin.helpdata.Parameter;
import lipstone.joshua.parser.plugin.types.InputFilterPlugin;
import lipstone.joshua.parser.plugin.types.OperationPlugin;
import lipstone.joshua.parser.types.BigDec;
import lipstone.joshua.parser.util.ConsCell;
import lipstone.joshua.parser.util.ConsType;
import org.apfloat.Apfloat;
public class Functions extends ParserPlugin implements OperationPlugin, InputFilterPlugin {
public BigDec round(ConsCell input) throws ParserException {
ConsCell num = parser.run(input);
if (num.length() != 1 || num.getCarType() != ConsType.NUMBER)
throw new UndefinedResultException("The argument to the round function must evaluate to a number", this);
Apfloat real = ((BigDec) num.getCar()).getInternal().real(), complex = ((BigDec) num.getCar()).getInternal().imag(), round = new Apfloat(0.5);
return new BigDec(real.frac().compareTo(round) == -1 ? real.floor() : real.ceil(), complex.frac().compareTo(round) == -1 ? complex.floor() : complex.ceil());
}
public BigDec factorial(ConsCell input) throws ParserException {
BigDec numFactorial = BigDec.ONE;
ArrayList<ConsCell> parts = input.splitOnSeparator();
if (parts.size() >= 2)
numFactorial = round(parts.get(1));
BigDec output = BigDec.ONE;
ConsCell num = parser.run(parts.get(0));
if (num.getCarType() != ConsType.NUMBER || !((BigDec) num.getCar()).isInt() || !((BigDec) num.getCar()).gteq(BigDec.ZERO))
throw new ParserException("The factorial operation is only defined for arguments that evaluate to whole numbers", this);
BigDec cap = (BigDec) num.getCar();
if (cap.gteq(numFactorial))
for (BigDec i = new BigDec(cap); i.gteq(BigDec.ONE); i = i.subtract(numFactorial))
output = output.multiply(i);
return output;
}
/**
* Summation or Sigma of some function
*
* @param input
* ConsCell of the form equation, minimum, maximum, [delta]
* @param parser
* the parser that called this method
* @return the sum of this equation from [min, max] with delta defaulting to 1, delta as passed otherwise
*/
public BigDec sum(ConsCell input) throws ParserException {
BigDec output = BigDec.ZERO, min = BigDec.ZERO, max = BigDec.ONE, delta = BigDec.ONE;
input = parser.run(input);
ArrayList<ConsCell> parts = input.splitOnSeparator();
if (parts.size() >= 3) {
if (parts.get(1).getCarType() != ConsType.NUMBER || parts.get(2).getCarType() != ConsType.NUMBER || (parts.size() >= 4 && parts.get(3).getCarType() != ConsType.NUMBER))
throw new UndefinedResultException("The sum function takes three or four arguments, the second and third and fourth of which must evaluate to numbers.", this);
min = (BigDec) parts.get(1).getCar();
max = (BigDec) parts.get(2).getCar();
if (parts.size() >= 4)
delta = (BigDec) parts.get(3).getCar();
output = sum(parts.get(0), min, max, delta);
}
return output;
}
private BigDec sum(ConsCell equation, BigDec min, BigDec max, BigDec delta) throws ParserException {
BigDec output = BigDec.ZERO;
if (min.gt(max)) {
BigDec temp = new BigDec(min);
min = new BigDec(max);
max = new BigDec(temp);
}
ArrayList<ConsCell> vars = equation.allInstancesOf(new ConsCell(parser.getVariables(equation).get(0), ConsType.IDENTIFIER));
for (BigDec i = new BigDec(min); i.lteq(max); i = i.add(delta)) {
for (ConsCell var : vars)
var.replaceCar(new ConsCell(i, ConsType.NUMBER));
output = output.add((BigDec) parser.run(equation).getCar());
}
return output;
}
private BigDec gcd(ConsCell input) throws ParserException {
ArrayList<ConsCell> ints = input.splitOnSeparator();
BigDec gcd = new BigDec(parser.run(ints.get(0)).toString());
for (int i = 1; i < ints.size(); i++)
gcd = gcd.gcd(new BigDec(parser.run(ints.get(i)).toString()));
return gcd;
}
private BigDec lcm(ConsCell input) throws ParserException {
ArrayList<ConsCell> ints = input.splitOnSeparator();
BigDec lcm = new BigDec(parser.run(ints.get(0)).toString());
for (int i = 1; i < ints.size(); i++)
lcm = lcm.lcm(new BigDec(parser.run(ints.get(i)).toString()));
return lcm;
}
/**
* Processes a log function incorporating bases other than 10
*
* @param num
* interior of the log function including a possible base
* @return the value of the log as a double.
* @throws ParserException
*/
public BigDec log(ConsCell num) throws ParserException {
num = parser.run(num);
ArrayList<ConsCell> sections = num.splitOnSeparator();
if ((sections.get(0).getCarType() != ConsType.NUMBER || sections.get(0).length() != 1) && (sections.size() == 1 || (sections.size() > 1 && (sections.get(1).getCarType() != ConsType.NUMBER ||
sections.get(1).length() != 1))))
throw new UndefinedResultException("The log function accepts either one or two arguments, both of which must evaluate to numbers.", this);
if (((BigDec) num.getCar()).lteq(BigDec.ZERO))
throw new UndefinedResultException("The log function is only defined for values greater than 0.", this);
return new BigDec(Math.log(((BigDec) (parser.run(sections.get(0)).getCar())).doubleValue()) / Math.log((sections.size() > 1 ?
(BigDec) parser.run(sections.get(1)).getCar() : new BigDec(parser.getDefaultLogBase())).doubleValue()));
}
public BigDec sigfigs(ConsCell num) throws ParserException {
String number = parser.run(num).toString();
BigDec sigfigs = BigDec.ZERO;
boolean start = false;
for (int i = 0; i < number.length(); i++) {
if (parser.isNumber(number.charAt(i))) {
if (!start && number.charAt(i) != '0')
start = true;
if (start)
sigfigs = sigfigs.pp();
}
}
return sigfigs;
}
public String scientificNotation(String num) {
String[] parts = scientificNotation(num, true);
if (parts[1].equals("0"))
return parts[0];
else if (parts[1].equals("1"))
return parts[0] + "*10";
else
return parts[0] + "*10^(" + parts[1] + ")";
}
public String[] scientificNotation(String num, boolean array) {
String parts[] = new String[]{"", ""};//base, order
if (!num.contains("."))
num = num + ".0";
while (num.charAt(0) == '0' && num.charAt(1) != '.')
num = num.substring(1);
if (num.length() > 1) {
for (int i = 0; i < num.length(); i++)
if (parser.isNumber(num.charAt(i)) && num.charAt(i) != '0' && num.charAt(i) != '.') {
parts[1] = new BigDec(num.indexOf('.') - i - 1).toString();
break;
}
parts[0] = num.substring(0, 1) + "." + num.substring(1).replaceAll("\\Q.\\E", "");
}
else
return new String[]{num, "0"};
return parts;
}
public String fromScientificNotation(String num) {
String exp = "0";
if (num.contains("E")) {
exp = num.substring(num.indexOf("E") + 1);
num = num.substring(0, num.indexOf("E"));
}
if (num.contains("10^")) {
exp = num.substring(num.indexOf("*10^") + 4);
num = num.substring(0, num.indexOf("*10^"));
}
if (num.contains(".")) {
int index = num.indexOf("."), expo = new Double(exp).intValue();
while (num.length() < expo + index + 1)
num = num + "0";
num = num.substring(0, index) + num.substring(index + 1, index + 1 + expo) + "." + num.substring(index + 1 + expo);
}
return num;
}
public BigDec phi(ConsCell num) throws ParserException {
BigDec output = BigDec.ZERO;
num = parser.run(num);
ArrayList<ConsCell> factors = parser.getCAS().primeFactor(num).splitOnSeparator();
String temp = num.toString();
for (ConsCell factor : factors)
temp = temp + "*(" + factor.toString() + "-1)";
temp = temp + "/(";
for (ConsCell factor : factors)
temp = temp + factor.toString() + "*";
temp = temp.substring(0, temp.length() - 1) + ")";
output = new BigDec(((BigDec) parser.run(parser.preProcess(Tokenizer.tokenizeString(temp))).getCar()).intValue());
return output;
}
public boolean isRelativelyPrime(ConsCell num) throws ParserException {
num = parser.run(num);
ArrayList<ConsCell> parts = num.splitOnSeparator();
BigDec num1 = BigDec.ZERO, num2 = BigDec.ZERO;
if (parts.get(0).getCarType() != ConsType.NUMBER || parts.get(1).getCarType() != ConsType.NUMBER)
throw new InvalidOperationException("Both arguments to isRelativelyPrime must be numbers.", this, null, null);
return isRelativelyPrime(num1, num2);
}
private boolean isRelativelyPrime(BigDec num1, BigDec num2) throws UndefinedResultException {
if (num2.gteq(num1))
return false;
boolean shareFactors = false;
ArrayList<BigDec> factors1 = parser.getCAS().findFactors(num1), factors2 = parser.getCAS().findFactors(num2);
for (BigDec a : factors1)
for (BigDec b : factors2)
if (a.gt(BigDec.ONE) && a.gt(BigDec.ONE) && a.eq(b))
shareFactors = true;
return !shareFactors;
}
@Override
public void loadOperations() throws PluginConflictException {
ArrayList<Parameter> params = new ArrayList<Parameter>();
params.add(new Parameter("x", "any real number", false));
addOperation(new Operation("round", params, "the number (x) rounded using the half-up method", "The half-up method is the method most commonly taught in schools.", this));
addOperation(new Operation("ceil", params, "the number (x) rounded up", "The ceiling function.", this));
addOperation(new Operation("floor", params, "the number (x) rounded down", "The floor function.", this));
params.add(new Parameter("decrement", "any positive, real integer, defaults to 1", true));
addOperation(new Operation("factorial", params, "the factorial of x", "when the decrement is 1, this is the standard factorial", this));
params.remove(1);
params.add(new Parameter("base", "any real integer, defaults to 10", true));
addOperation(new Operation("log", params, "the log [base] of x", "", this));
params.remove(1);
addOperation(new Operation("ln", params, "the natural log of x", "the natural log of x is equivalent to log(x, e)", this));
params.remove(0);
params.add(new Parameter("equation", "some algebraic expression", false));
params.add(new Parameter("min", "minimum bound for the sum", false));
params.add(new Parameter("max", "maximum bound for the sum", false));
params.add(new Parameter("delta", "the step size for the sum, defaults to 1", true));
addOperation(new Operation("sigma", params, "the sum of the function from [min, max]", "", this));
addOperation(new Operation("sigma", params, "the sum of the function from [min, max]", "", this));
params.clear();
params.add(new Parameter("x", "any whole number", false));
addOperation(new Operation("phi", params, "the number of whole numbers relatively prime to x", "Credits to Jacob for telling me about this function and helping me make it more efficient.", this));
params.add(new Parameter("y", "any whole number", false));
addOperation(new Operation("isRelativelyPrime", params, "whether x is relatively prime to y", "Credits to Jacob for telling me about this function.", new String[]{"relativelyPrime"}, this));
params.clear();
params.add(new Parameter("x", "any real number", false));
addOperation(new Operation("sigfigs", params, "the number of significant figures in x", "uses the standard calculation methods", this));
params.clear();
params.add(new Parameter("integers", "a list of integers", false));
addOperation(new Operation("gcd", params, "the greatest common divisor of the listed integers", "Credits to Jacob for explaining the highly efficient algorithm that makes this rational to implement.", this));
addOperation(new Operation("lcm", params, "the least common multiple of the listed integers", "Credits to Jacob for explaining the highly efficient algorithm that makes this rational to implement.", this));
}
@Override
public ConsCell runOperation(String operation, ConsCell input) throws ParserException {
int numParams = input.splitOnSeparator().size();
if (operation.equals("round"))
return new ConsCell(round(input), ConsType.NUMBER);
if (operation.equals("ceil"))
return new ConsCell(new BigDec(Math.ceil(new Double(parser.run(input).toString()))), ConsType.NUMBER);
if (operation.equals("floor"))
return new ConsCell(new BigDec(Math.floor(new Double(parser.run(input).toString()))), ConsType.NUMBER);
if (operation.equals("factorial"))
return new ConsCell(factorial(input), ConsType.NUMBER);
if (operation.equals("log"))
return new ConsCell(log(input), ConsType.NUMBER);
if (operation.equals("ln"))
return new ConsCell(log(input.append(new ConsCell(",", ConsType.SEPARATOR, new ConsCell("e", ConsType.IDENTIFIER), ConsType.CONS_CELL)).getFirstConsCell()), ConsType.NUMBER);
if ((operation.equals("sum") || operation.equals("sigma")) && (numParams == 3 || numParams == 4))
return new ConsCell(sum(input), ConsType.NUMBER);
if (operation.equals("phi"))
return new ConsCell(phi(input), ConsType.NUMBER);
if (operation.equals("isRelativelyPrime") || operation.equals("relativelyPrime") && numParams == 2)
return new ConsCell(new Boolean(isRelativelyPrime(input)).toString(), ConsType.IDENTIFIER);
if (operation.equals("sigfigs"))
return new ConsCell(sigfigs(input), ConsType.NUMBER);
if (operation.equals("gcd"))
return new ConsCell(gcd(input), ConsType.NUMBER);
if (operation.equals("lcm"))
return new ConsCell(lcm(input), ConsType.NUMBER);
return null;
}
@Override
public void unloadOperations() {/* Nothing to do here */}
@Override
public ConsCell preProcess(ConsCell input) throws ParserException {
Pattern factorial = Pattern.compile("[!]+");
ConsCell current = input;
if (current.getCdrType() == ConsType.CONS_CELL)
current = current.getNextConsCell();
do {
if (current.getCarType() != ConsType.IDENTIFIER)
continue;
Matcher m = factorial.matcher((String) current.getCar());
if (m.matches()) {
//This line produces the following structure: factorial ([data before !'s] , [number of !'s])
ConsCell replacement = new ConsCell("factorial", ConsType.IDENTIFIER, new ConsCell(new ConsCell(current.getPreviousConsCell().getCar(), current.getPreviousConsCell().getCarType(),
new ConsCell(",", ConsType.SEPARATOR, new ConsCell(new BigDec(((String) current.getCar()).length()), ConsType.NUMBER))), ConsType.CONS_CELL));
if (current.getPreviousConsCell() == input) {
replacement.getLastConsCell().append(input.getNextConsCell(2));
input = current = replacement;
}
else {
current = current.getPreviousConsCell().remove();
current.replaceCar(replacement);
}
}
} while (!((current = current.getNextConsCell()).isNull())); //This steps current forward while checking for nulls
return input; //Returns the first ConsCell in this list
}
@Override
public void loadInputFilter() {/* Nothing to do here */}
@Override
public void unloadInputFilter() {/* Nothing to do here */}
}