-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
karl-zschiebsch
committed
Sep 18, 2023
1 parent
a21e44e
commit 84c547d
Showing
18 changed files
with
472 additions
and
19 deletions.
There are no files selected for viewing
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package io.scvis; | ||
|
||
import javax.annotation.Nonnull; | ||
import java.util.function.BiFunction; | ||
import java.util.function.BinaryOperator; | ||
|
||
public class BaseOperator implements Operator { | ||
|
||
public static final Operator ADD = new BaseOperator((a, b) -> | ||
new Constant(a.get().doubleValue() + b.get().doubleValue()), 1) { | ||
@Override | ||
public boolean zeroAsLeft() { | ||
return true; | ||
} | ||
}; | ||
public static final Operator SUBTRACT = new BaseOperator((a, b) -> | ||
new Constant(a.get().doubleValue() - b.get().doubleValue()), 1) { | ||
@Override | ||
public boolean zeroAsLeft() { | ||
return true; | ||
} | ||
}; | ||
public static final Operator MULTIPLY = new BaseOperator((a, b) -> | ||
new Constant(a.get().doubleValue() * b.get().doubleValue()), 2); | ||
public static final Operator DIVIDE = new BaseOperator((a, b) -> | ||
new Constant(a.get().doubleValue() / b.get().doubleValue()), 2); | ||
public static final Operator MOD = new BaseOperator((a, b) -> | ||
new Constant(a.get().doubleValue() % b.get().doubleValue()), 3); | ||
public static final Operator POW = new BaseOperator((a, b) -> | ||
new Constant(Math.pow(a.get().doubleValue(), b.get().doubleValue())), 4); | ||
|
||
private final @Nonnull BiFunction<Value, Value, Value> function; | ||
|
||
private final int priority; | ||
|
||
public BaseOperator(@Nonnull BinaryOperator<Value> function, int priority) { | ||
this.function = function; | ||
this.priority = priority; | ||
} | ||
|
||
@Nonnull | ||
@Override | ||
public Value evaluate(@Nonnull Value left, @Nonnull Value right) { | ||
return function.apply(left, right); | ||
} | ||
|
||
@Override | ||
public int priority() { | ||
return priority; | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package io.scvis; | ||
|
||
import javax.annotation.Nonnull; | ||
import java.util.List; | ||
|
||
public class Brackets implements Value { | ||
|
||
private final @Nonnull Number value; | ||
|
||
public Brackets(@Nonnull List<Operator> operators, @Nonnull List<Object> tokens) { | ||
value = new TokenEvaluator(operators, tokens).evaluate(); | ||
} | ||
|
||
@Nonnull | ||
@Override | ||
public Number get() { | ||
return value; | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package io.scvis; | ||
|
||
import javax.annotation.Nonnull; | ||
import java.util.Map; | ||
|
||
public class Constant implements Value { | ||
private final @Nonnull Number value; | ||
|
||
public static final Map<String, Constant> CONSTANT_MAP = Map.of( | ||
"e", new Constant(Math.E), "pi", new Constant(Math.PI)); | ||
|
||
public static final Constant ZERO = new Constant(0.0); | ||
|
||
public Constant(@Nonnull Number value) { | ||
this.value = value; | ||
} | ||
|
||
@Nonnull | ||
@Override | ||
public Number get() { | ||
return value; | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package io.scvis; | ||
|
||
import javax.annotation.Nonnull; | ||
import java.util.Map; | ||
import java.util.function.DoubleFunction; | ||
|
||
public class Function implements Value { | ||
|
||
private final @Nonnull Number value; | ||
|
||
public static final @Nonnull Map<String, DoubleFunction<Number>> DOUBLE_FUNCTION_MAP = Map.of( | ||
"sin", Math::sin, "cos", Math::cos, "tan", Math::tan, | ||
"asin", Math::asin, "acos", Math::acos, "atan", Math::atan, | ||
"sqrt", Math::sqrt, "abs", Math::abs, "signum", Math::signum | ||
); | ||
|
||
public Function(@Nonnull String name, @Nonnull Brackets brackets) throws NoSuchMethodException { | ||
DoubleFunction<Number> function = DOUBLE_FUNCTION_MAP.get(name); | ||
if (function == null) | ||
throw new NoSuchMethodException("No method found for " + name); | ||
value = function.apply(brackets.get().doubleValue()); | ||
} | ||
|
||
@Nonnull | ||
@Override | ||
public Number get() { | ||
return value; | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package io.scvis; | ||
|
||
import javax.annotation.Nonnull; | ||
|
||
public interface Operator extends Comparable<Operator> { | ||
|
||
@Nonnull Value evaluate(@Nonnull Value left, @Nonnull Value right); | ||
|
||
default boolean zeroAsLeft() { | ||
return false; | ||
} | ||
|
||
int priority(); | ||
|
||
@Override | ||
default int compareTo(@Nonnull Operator o) { | ||
return o.priority() - priority(); | ||
} | ||
|
||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package io.scvis; | ||
|
||
import javax.annotation.Nonnull; | ||
import java.util.*; | ||
|
||
public class TokenEvaluator { | ||
|
||
private final @Nonnull List<Operator> operators; | ||
private final @Nonnull List<Object> tokens; | ||
|
||
public TokenEvaluator(@Nonnull List<Operator> operators, @Nonnull List<Object> tokens) { | ||
this.operators = operators; | ||
Collections.sort(operators); | ||
this.tokens = tokens; | ||
} | ||
|
||
private void eliminate() { | ||
for (Operator operator : operators) { | ||
int index = tokens.indexOf(operator); | ||
if (index == 0) { | ||
if (operator.zeroAsLeft()) { | ||
Value evaluated = operator.evaluate(Constant.ZERO, (Value) tokens.get(1)); | ||
tokens.remove(1); | ||
tokens.remove(0); | ||
tokens.add(0, evaluated); | ||
} else { | ||
throw new UnsupportedOperationException("Operator does not allow zero as left!"); | ||
} | ||
} else { | ||
Value evaluated = operator.evaluate((Value) tokens.get(index - 1), (Value) tokens.get(index + 1)); | ||
for (int i = 1; i > -2; i--) { | ||
tokens.remove(index + i); | ||
} | ||
tokens.add(index - 1, evaluated); | ||
} | ||
} | ||
} | ||
|
||
public Number evaluate() { | ||
eliminate(); | ||
if (tokens.size() != 1) { | ||
throw new ArithmeticException("False number of tokens are left after elimination: " + tokens.size()); | ||
} | ||
return ((Value) tokens.get(0)).get(); | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
package io.scvis; | ||
|
||
import javax.annotation.CheckReturnValue; | ||
import javax.annotation.Nonnull; | ||
import java.text.ParseException; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
public class TokenParser { | ||
|
||
private static final @Nonnull Map<Character, Operator> CHARACTER_OPERATOR_MAP = Map.of( | ||
'+', BaseOperator.ADD, '-', BaseOperator.SUBTRACT, '*', BaseOperator.MULTIPLY, | ||
'/', BaseOperator.DIVIDE, '%', BaseOperator.MOD, '^', BaseOperator.POW); | ||
|
||
private int pos; | ||
|
||
private final @Nonnull List<Operator> operators = new ArrayList<>(); | ||
|
||
private final @Nonnull List<Object> tokens = new ArrayList<>(); | ||
|
||
public void tokenize(String string) throws ParseException, NoSuchMethodException { | ||
char[] chars = string.toCharArray(); | ||
pos = 0; // reset position | ||
while (pos < chars.length) { | ||
char c = chars[pos]; | ||
if (Character.isDigit(c) || c == '.') { | ||
tokens.add(parseNumber(chars)); | ||
} else if (Character.isAlphabetic(c)) { | ||
tokens.add(parseText(chars)); | ||
} else if (CHARACTER_OPERATOR_MAP.containsKey(c)) { | ||
Operator operator = CHARACTER_OPERATOR_MAP.get(c); | ||
tokens.add(operator); | ||
operators.add(operator); | ||
} else if (c == '(') { | ||
tokens.add(parseBrackets(chars)); | ||
} else if (!Character.isSpaceChar(c)) { | ||
throw new ParseException("Could not parse char: ", pos); | ||
} | ||
pos++; | ||
} | ||
} | ||
|
||
@CheckReturnValue | ||
@Nonnull | ||
private Value parseVar(String name) throws NoSuchMethodException { | ||
Constant constant = Constant.CONSTANT_MAP.get(name.toLowerCase()); | ||
if (constant == null) | ||
throw new NoSuchMethodException("No variable found for: " + name); | ||
return constant; | ||
} | ||
|
||
@CheckReturnValue | ||
@Nonnull | ||
private Brackets parseBrackets(char[] chars) throws ParseException, NoSuchMethodException { | ||
StringBuilder body = new StringBuilder("("); | ||
int open = 1; | ||
pos++; | ||
while (open > 0) { | ||
char c = chars[pos]; | ||
if (c == '(') open++; | ||
else if (c == ')') open--; | ||
body.append(c); | ||
pos++; | ||
} | ||
pos--; | ||
TokenParser parser = new TokenParser(); | ||
parser.tokenize(body.substring(1, body.length() - 1)); | ||
return new Brackets(parser.operators, parser.tokens); | ||
} | ||
|
||
@CheckReturnValue | ||
@Nonnull | ||
private Value parseFunction(String name, char[] chars) throws ParseException, NoSuchMethodException { | ||
return new Function(name, parseBrackets(chars)); | ||
} | ||
|
||
@CheckReturnValue | ||
@Nonnull | ||
private Value parseText(char[] chars) throws ParseException, NoSuchMethodException { | ||
StringBuilder build = new StringBuilder(); | ||
while (pos < chars.length && (Character.isAlphabetic(chars[pos]) || Character.isDigit(chars[pos]))) { | ||
build.append(chars[pos]); | ||
pos++; | ||
} | ||
if (pos < chars.length && chars[pos] == '(') { | ||
return parseFunction(build.toString(), chars); | ||
} else { | ||
pos--; | ||
return parseVar(build.toString()); | ||
} | ||
} | ||
|
||
@CheckReturnValue | ||
@Nonnull | ||
private Value parseNumber(char[] chars) throws ParseException { | ||
double build = 0; | ||
int real = -1; | ||
while (pos < chars.length) { | ||
char c = chars[pos]; | ||
if (Character.isDigit(c)) { | ||
if (real > -1) { | ||
build += Character.digit(c, 10) / Math.pow(10, pos - real); | ||
} else { | ||
build = build * 10.0 + Character.digit(c, 10); | ||
} | ||
} else if (c == '.') { | ||
if (real > -1) | ||
throw new ParseException("Could not parse number: ", pos); | ||
real = pos; | ||
} else { | ||
pos--; | ||
break; | ||
} | ||
pos++; | ||
} | ||
return new Constant(build); | ||
} | ||
|
||
@Nonnull | ||
public List<Operator> getOperators() { | ||
return operators; | ||
} | ||
|
||
@Nonnull | ||
public List<Object> getTokens() { | ||
return tokens; | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package io.scvis; | ||
|
||
import javax.annotation.Nonnull; | ||
|
||
public interface Value { | ||
|
||
@Nonnull Number get(); | ||
} |
Oops, something went wrong.