postfix functions

This commit is contained in:
Sergey Solovyev 2011-09-26 13:03:54 +04:00
parent 67bd764c92
commit e81a1ef27b
7 changed files with 327 additions and 125 deletions

View File

@ -25,19 +25,22 @@ public class CalculatorModel {
private int NUMBER_OF_FRACTION_DIGITS = 5; private int NUMBER_OF_FRACTION_DIGITS = 5;
@NotNull
public Preprocessor preprocessor = new ToJsclPreprocessor();
public CalculatorModel() throws EvalError { public CalculatorModel() throws EvalError {
interpreter = new Interpreter(); interpreter = new Interpreter();
interpreter.eval(Preprocessor.wrap(JsclOperation.importCommands, "/jscl/editorengine/commands")); interpreter.eval(ToJsclPreprocessor.wrap(JsclOperation.importCommands, "/jscl/editorengine/commands"));
} }
public String evaluate(@NotNull JsclOperation operation, @NotNull String expression) throws EvalError, ParseException { public String evaluate(@NotNull JsclOperation operation, @NotNull String expression) throws EvalError, ParseException {
final String preprocessedExpression = Preprocessor.process(expression); final String preprocessedExpression = preprocessor.process(expression);
//Log.d(CalculatorModel.class.getName(), "Preprocessed expression: " + preprocessedExpression); //Log.d(CalculatorModel.class.getName(), "Preprocessed expression: " + preprocessedExpression);
Object evaluationObject = interpreter.eval(Preprocessor.wrap(operation, preprocessedExpression)); Object evaluationObject = interpreter.eval(ToJsclPreprocessor.wrap(operation, preprocessedExpression));
String result = String.valueOf(evaluationObject).trim(); String result = String.valueOf(evaluationObject).trim();
try { try {

View File

@ -1,125 +1,14 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.android.calculator; package org.solovyev.android.calculator;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.calculator.math.Functions;
import org.solovyev.android.calculator.math.MathEntityType;
import org.solovyev.common.utils.CollectionsUtils;
import org.solovyev.common.utils.Finder;
public class Preprocessor { /**
* User: serso
* Date: 9/26/11
* Time: 12:12 PM
*/
public interface Preprocessor {
@NotNull @NotNull
public static String process(@NotNull String s) { String process(@NotNull String s);
final StringBuilder sb = new StringBuilder();
final StartWithFinder startsWithFinder = new StartWithFinder(s);
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
checkMultiplicationSignBeforeFunction(sb, s, i);
if (MathEntityType.openGroupSymbols.contains(ch)) {
sb.append('(');
} else if (MathEntityType.closeGroupSymbols.contains(ch)) {
sb.append(')');
} else if (ch == 'π') {
sb.append("pi");
} else if (ch == '×' || ch == '∙') {
sb.append("*");
} else {
startsWithFinder.setI(i);
final String function = CollectionsUtils.get(MathEntityType.functions, startsWithFinder);
if (function != null) {
sb.append(toJsclFunction(function));
i += function.length() - 1;
} else if (ch == 'e') {
sb.append("exp(1)");
} else if (ch == 'i') {
sb.append("sqrt(-1)");
} else {
sb.append(ch);
}
}
}
return sb.toString();
}
@NotNull
private static String toJsclFunction(@NotNull String function) {
final String result;
if (function.equals(Functions.LN)) {
result = Functions.LOG;
} else if (function.equals(Functions.SQRT_SIGN)) {
result = Functions.SQRT;
} else {
result = function;
}
return result;
}
private static class StartWithFinder implements Finder<String> {
private int i;
@NotNull
private final String targetString;
private StartWithFinder(@NotNull String targetString) {
this.targetString = targetString;
}
@Override
public boolean isFound(@Nullable String s) {
return targetString.startsWith(s, i);
}
public void setI(int i) {
this.i = i;
}
}
private static void checkMultiplicationSignBeforeFunction(@NotNull StringBuilder sb, @NotNull String s, int i) {
if (i > 0) {
// get character before function
char chBefore = s.charAt(i - 1);
char ch = s.charAt(i);
final MathEntityType mathTypeBefore = MathEntityType.getType(String.valueOf(chBefore));
final MathEntityType mathType = MathEntityType.getType(String.valueOf(ch));
if (mathTypeBefore != MathEntityType.binary_operation &&
mathTypeBefore != MathEntityType.unary_operation &&
mathTypeBefore != MathEntityType.function &&
!MathEntityType.openGroupSymbols.contains(chBefore)) {
if (mathType == MathEntityType.constant) {
sb.append("*");
} else if (MathEntityType.openGroupSymbols.contains(ch) && mathTypeBefore != null) {
sb.append("*");
} else if (mathType == MathEntityType.digit && mathTypeBefore != MathEntityType.digit && mathTypeBefore != MathEntityType.dot) {
sb.append("*");
} else {
for (String function : MathEntityType.functions) {
if (s.startsWith(function, i)) {
sb.append("*");
break;
}
}
}
}
}
}
public static String wrap(@NotNull JsclOperation operation, @NotNull String s) {
return operation.name() + "(\"" + s + "\");";
}
} }

View File

@ -0,0 +1,202 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.android.calculator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.calculator.math.Functions;
import org.solovyev.android.calculator.math.MathEntityType;
import org.solovyev.common.utils.CollectionsUtils;
import org.solovyev.common.utils.FilterType;
import org.solovyev.common.utils.Finder;
public class ToJsclPreprocessor implements Preprocessor {
@Override
@NotNull
public String process(@NotNull String s) {
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
if ( MathEntityType.getType(ch) == MathEntityType.postfix_function ) {
int start = getPostfixFunctionStart(s, i - 1);
}
}
final StartsWithFinder startsWithFinder = new StartsWithFinder(s);
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
checkMultiplicationSignBeforeFunction(sb, s, i);
if (MathEntityType.openGroupSymbols.contains(ch)) {
sb.append('(');
} else if (MathEntityType.closeGroupSymbols.contains(ch)) {
sb.append(')');
} else if (ch == 'π') {
sb.append("pi");
} else if (ch == '×' || ch == '∙') {
sb.append("*");
} else {
startsWithFinder.setI(i);
final String function = CollectionsUtils.get(MathEntityType.prefixFunctions, startsWithFinder);
if (function != null) {
sb.append(toJsclFunction(function));
i += function.length() - 1;
} else if (ch == 'e') {
sb.append("exp(1)");
} else if (ch == 'i') {
sb.append("sqrt(-1)");
} else {
sb.append(ch);
}
}
}
return sb.toString();
}
public int getPostfixFunctionStart(@NotNull String s, int position) {
assert s.length() > position;
int numberOfOpenGroups = 0;
int result = position;
for ( ; result >= 0; result-- ) {
char ch = s.charAt(result);
final MathEntityType mathEntityType = MathEntityType.getType(ch);
if ( mathEntityType != null ) {
if ( CollectionsUtils.contains(mathEntityType, MathEntityType.digit, MathEntityType.dot) ) {
// continue
} else if (MathEntityType.closeGroupSymbols.contains(ch)) {
numberOfOpenGroups++;
} else if (MathEntityType.openGroupSymbols.contains(ch)) {
numberOfOpenGroups--;
} else {
if (stop(s, numberOfOpenGroups, result)) break;
}
} else {
if (stop(s, numberOfOpenGroups, result)) break;
}
}
return result;
}
private boolean stop(String s, int numberOfOpenGroups, int i) {
if ( numberOfOpenGroups == 0 ) {
if (i > 0) {
final EndsWithFinder endsWithFinder = new EndsWithFinder(s);
endsWithFinder.setI(i+1);
if ( !CollectionsUtils.contains(MathEntityType.prefixFunctions, FilterType.included, endsWithFinder) ) {
MathEntityType type = MathEntityType.getType(s.charAt(i));
if (type != null && type != MathEntityType.constant) {
return true;
}
}
} else {
return true;
}
}
return false;
}
@NotNull
private static String toJsclFunction(@NotNull String function) {
final String result;
if (function.equals(Functions.LN)) {
result = Functions.LOG;
} else if (function.equals(Functions.SQRT_SIGN)) {
result = Functions.SQRT;
} else {
result = function;
}
return result;
}
private static class StartsWithFinder implements Finder<String> {
private int i;
@NotNull
private final String targetString;
private StartsWithFinder(@NotNull String targetString) {
this.targetString = targetString;
}
@Override
public boolean isFound(@Nullable String s) {
return targetString.startsWith(s, i);
}
public void setI(int i) {
this.i = i;
}
}
private static class EndsWithFinder implements Finder<String> {
private int i;
@NotNull
private final String targetString;
private EndsWithFinder(@NotNull String targetString) {
this.targetString = targetString;
}
@Override
public boolean isFound(@Nullable String s) {
return targetString.substring(0, i).endsWith(s);
}
public void setI(int i) {
this.i = i;
}
}
private static void checkMultiplicationSignBeforeFunction(@NotNull StringBuilder sb, @NotNull String s, int i) {
if (i > 0) {
// get character before function
char chBefore = s.charAt(i - 1);
char ch = s.charAt(i);
final MathEntityType mathTypeBefore = MathEntityType.getType(String.valueOf(chBefore));
final MathEntityType mathType = MathEntityType.getType(String.valueOf(ch));
if (mathTypeBefore != MathEntityType.binary_operation &&
mathTypeBefore != MathEntityType.unary_operation &&
mathTypeBefore != MathEntityType.function &&
!MathEntityType.openGroupSymbols.contains(chBefore)) {
if (mathType == MathEntityType.constant) {
sb.append("*");
} else if (MathEntityType.openGroupSymbols.contains(ch) && mathTypeBefore != null) {
sb.append("*");
} else if (mathType == MathEntityType.digit && mathTypeBefore != MathEntityType.digit && mathTypeBefore != MathEntityType.dot) {
sb.append("*");
} else {
for (String function : MathEntityType.prefixFunctions) {
if (s.startsWith(function, i)) {
sb.append("*");
break;
}
}
}
}
}
}
public static String wrap(@NotNull JsclOperation operation, @NotNull String s) {
return operation.name() + "(\"" + s + "\");";
}
}

View File

@ -0,0 +1,54 @@
package org.solovyev.android.calculator.math;
import jscl.math.Generic;
import jscl.math.JSCLInteger;
import jscl.math.NotIntegrableException;
import jscl.math.Variable;
import jscl.math.function.Function;
/**
* User: serso
* Date: 9/26/11
* Time: 12:58 PM
*/
public class Factorial extends Function {
public Factorial(Generic[] parameter) {
super("fact", parameter);
}
@Override
public Generic evaluate() {
return expressionValue();
}
@Override
public Generic evalelem() {
return null;
}
@Override
public Generic evalsimp() {
return null;
}
@Override
public Generic evalnum() {
return null;
}
@Override
public Generic antiderivative(int n) throws NotIntegrableException {
throw new UnsupportedOperationException("Not implemented yet!");
}
@Override
public Generic derivative(int n) {
throw new UnsupportedOperationException("Not implemented yet!");
}
@Override
protected Variable newinstance() {
return new Factorial(null);
}
}

View File

@ -36,5 +36,10 @@ public interface Functions {
String SQRT_SIGN = ""; String SQRT_SIGN = "";
String SQRT = "sqrt"; String SQRT = "sqrt";
public static final List<String> all = Arrays.asList(SIN, SINH, ASIN, ASINH, COS, COSH, ACOS, ACOSH, TAN, TANH, ATAN, ATANH, LOG, LN, MOD, SQRT, SQRT_SIGN, EXP); public static final List<String> allPrefix = Arrays.asList(SIN, SINH, ASIN, ASINH, COS, COSH, ACOS, ACOSH, TAN, TANH, ATAN, ATANH, LOG, LN, MOD, SQRT, SQRT_SIGN, EXP);
Character FACT = '!';
Character DEGREE = '°';
public static final List<Character> allPostfix = Arrays.asList(FACT, DEGREE);
} }

View File

@ -19,6 +19,7 @@ public enum MathEntityType {
constant, constant,
dot, dot,
function, function,
postfix_function,
unary_operation, unary_operation,
binary_operation, binary_operation,
group_symbols, group_symbols,
@ -26,13 +27,15 @@ public enum MathEntityType {
public static final List<Character> constants = Arrays.asList('e', 'π', 'i'); public static final List<Character> constants = Arrays.asList('e', 'π', 'i');
public static final List<Character> dots = Arrays.asList('.', ','); public static final List<Character> dots = Arrays.asList('.');
public static final List<Character> unaryOperations = Arrays.asList('-', '=', '!'); public static final List<Character> unaryOperations = Arrays.asList('-', '=', '!');
public static final List<Character> binaryOperations = Arrays.asList('-', '+', '*', '×', '∙', '/', '^' ); public static final List<Character> binaryOperations = Arrays.asList('-', '+', '*', '×', '∙', '/', '^' );
public static final List<String> functions = Functions.all; public static final List<String> prefixFunctions = Functions.allPrefix;
public static final List<Character> postfixFunctions = Functions.allPostfix;
public static final List<String> groupSymbols = Arrays.asList("[]", "()", "{}"); public static final List<String> groupSymbols = Arrays.asList("[]", "()", "{}");
@ -57,7 +60,7 @@ public enum MathEntityType {
} }
if ( result == null ) { if ( result == null ) {
if ( functions.contains(s) ) { if ( prefixFunctions.contains(s) ) {
result = MathEntityType.function; result = MathEntityType.function;
} else if ( groupSymbols.contains(s) ) { } else if ( groupSymbols.contains(s) ) {
result = MathEntityType.group_symbols; result = MathEntityType.group_symbols;
@ -74,6 +77,8 @@ public enum MathEntityType {
if ( Character.isDigit(ch) ) { if ( Character.isDigit(ch) ) {
result = MathEntityType.digit; result = MathEntityType.digit;
} else if ( postfixFunctions.contains(ch) ) {
result = MathEntityType.postfix_function;
} else if ( unaryOperations.contains(ch) ) { } else if ( unaryOperations.contains(ch) ) {
result = MathEntityType.unary_operation; result = MathEntityType.unary_operation;
} else if ( binaryOperations.contains(ch) ) { } else if ( binaryOperations.contains(ch) ) {

View File

@ -0,0 +1,44 @@
package org.solovyev.android.calculator;
import org.junit.Assert;
import org.junit.Test;
/**
* User: serso
* Date: 9/26/11
* Time: 12:13 PM
*/
public class ToJsclPreprocessorTest {
@Test
public void testProcess() throws Exception {
final ToJsclPreprocessor preprocessor = new ToJsclPreprocessor();
Assert.assertEquals( "sin(4)*cos(5)", preprocessor.process("sin(4)cos(5)"));
Assert.assertEquals( "pi*sin(4)*pi*cos(sqrt(5))", preprocessor.process("πsin(4)πcos(√(5))"));
Assert.assertEquals( "pi*sin(4)+pi*cos(sqrt(5))", preprocessor.process("πsin(4)+πcos(√(5))"));
Assert.assertEquals( "pi*sin(4)+pi*cos(sqrt(5+sqrt(-1)))", preprocessor.process("πsin(4)+πcos(√(5+i))"));
Assert.assertEquals( "pi*sin(4.01)+pi*cos(sqrt(5+sqrt(-1)))", preprocessor.process("πsin(4.01)+πcos(√(5+i))"));
Assert.assertEquals( "exp(1)^pi*sin(4.01)+pi*cos(sqrt(5+sqrt(-1)))", preprocessor.process("e^πsin(4.01)+πcos(√(5+i))"));
}
@Test
public void testPostfixFunctionsProcessing() throws Exception {
final ToJsclPreprocessor preprocessor = new ToJsclPreprocessor();
Assert.assertEquals(-1, preprocessor.getPostfixFunctionStart("5!", 0));
Assert.assertEquals(0, preprocessor.getPostfixFunctionStart("!", 0));
Assert.assertEquals(-1, preprocessor.getPostfixFunctionStart("5.4434234!", 8));
Assert.assertEquals(1, preprocessor.getPostfixFunctionStart("2+5!", 2));
Assert.assertEquals(4, preprocessor.getPostfixFunctionStart("2.23+5.4434234!", 13));
Assert.assertEquals(14, preprocessor.getPostfixFunctionStart("2.23+5.4434234*5!", 15));
Assert.assertEquals(14, preprocessor.getPostfixFunctionStart("2.23+5.4434234*5.1!", 17));
Assert.assertEquals(4, preprocessor.getPostfixFunctionStart("2.23+(5.4434234*5.1)!", 19));
Assert.assertEquals(4, preprocessor.getPostfixFunctionStart("2.23+(5.4434234*(5.1+1))!", 23));
Assert.assertEquals(4, preprocessor.getPostfixFunctionStart("2.23+(5.4434234*sin(5.1+1))!", 26));
Assert.assertEquals(0, preprocessor.getPostfixFunctionStart("sin(5)!", 5));
Assert.assertEquals(0, preprocessor.getPostfixFunctionStart("sin(5sin(5sin(5)))!", 17));
Assert.assertEquals(1, preprocessor.getPostfixFunctionStart("2+sin(5sin(5sin(5)))!", 19));
Assert.assertEquals(4, preprocessor.getPostfixFunctionStart("2.23+sin(5.4434234*sin(5.1+1))!", 29));
}
}