first commit
This commit is contained in:
BIN
artifacts/queue/ArrayQueueTest.jar
Normal file
BIN
artifacts/queue/ArrayQueueTest.jar
Normal file
Binary file not shown.
BIN
artifacts/queue/QueueTest.jar
Normal file
BIN
artifacts/queue/QueueTest.jar
Normal file
Binary file not shown.
BIN
artifacts/search/BinarySearchTest.jar
Normal file
BIN
artifacts/search/BinarySearchTest.jar
Normal file
Binary file not shown.
92
common/base/Asserts.java
Normal file
92
common/base/Asserts.java
Normal file
@@ -0,0 +1,92 @@
|
||||
package base;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
|
||||
public final class Asserts {
|
||||
static {
|
||||
Locale.setDefault(Locale.US);
|
||||
}
|
||||
|
||||
private Asserts() {
|
||||
}
|
||||
|
||||
public static void assertEquals(final String message, final Object expected, final Object actual) {
|
||||
final String reason = String.format("%s:%n expected `%s`,%n actual `%s`",
|
||||
message, toString(expected), toString(actual));
|
||||
assertTrue(reason, Objects.deepEquals(expected, actual));
|
||||
}
|
||||
|
||||
public static String toString(final Object value) {
|
||||
if (value != null && value.getClass().isArray()) {
|
||||
final String result = Arrays.deepToString(new Object[]{value});
|
||||
return result.substring(1, result.length() - 1);
|
||||
} else {
|
||||
return Objects.toString(value);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> void assertEquals(final String message, final List<T> expected, final List<T> actual) {
|
||||
for (int i = 0; i < Math.min(expected.size(), actual.size()); i++) {
|
||||
assertEquals(message + ":" + (i + 1), expected.get(i), actual.get(i));
|
||||
}
|
||||
assertEquals(message + ": Number of items", expected.size(), actual.size());
|
||||
}
|
||||
|
||||
public static void assertTrue(final String message, final boolean value) {
|
||||
if (!value) {
|
||||
throw error("%s", message);
|
||||
}
|
||||
}
|
||||
|
||||
public static void assertEquals(final String message, final double expected, final double actual, final double precision) {
|
||||
assertTrue(
|
||||
String.format("%s: Expected %.12f, found %.12f", message, expected, actual),
|
||||
isEqual(expected, actual, precision)
|
||||
);
|
||||
}
|
||||
|
||||
private static boolean is(final double expected, final double actual, final double precision) {
|
||||
return Math.abs(expected - actual) < precision;
|
||||
}
|
||||
|
||||
public static boolean isEqual(final double expected, final double actual, final double precision) {
|
||||
final double error = Math.abs(actual - expected);
|
||||
return error <= precision
|
||||
|| error <= precision * Math.abs(expected)
|
||||
|| !Double.isFinite(expected)
|
||||
|| Math.abs(expected) > 1e100
|
||||
|| Math.abs(expected) < precision && !Double.isFinite(actual)
|
||||
|| is(Math.PI, expected, precision) && is(0, actual, precision)
|
||||
|| is(0, expected, precision) && is(Math.PI, actual, precision)
|
||||
|| is(-Math.PI, expected, precision) && is(Math.PI, actual, precision)
|
||||
|| is(Math.PI, expected, precision) && is(-Math.PI, actual, precision);
|
||||
}
|
||||
|
||||
public static void assertSame(final String message, final Object expected, final Object actual) {
|
||||
assertTrue(String.format("%s: expected same objects: %s and %s", message, expected, actual), expected == actual);
|
||||
}
|
||||
|
||||
public static void checkAssert(final Class<?> c) {
|
||||
if (!c.desiredAssertionStatus()) {
|
||||
throw error("You should enable assertions by running 'java -ea %s'", c.getName());
|
||||
}
|
||||
}
|
||||
|
||||
public static AssertionError error(final String format, final Object... args) {
|
||||
final String message = String.format(format, args);
|
||||
return args.length > 0 && args[args.length - 1] instanceof Throwable
|
||||
? new AssertionError(message, (Throwable) args[args.length - 1])
|
||||
: new AssertionError(message);
|
||||
}
|
||||
|
||||
public static void printStackTrace(final String message) {
|
||||
new Exception(message).printStackTrace(System.out);
|
||||
}
|
||||
}
|
||||
20
common/base/BaseChecker.java
Normal file
20
common/base/BaseChecker.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package base;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public abstract class BaseChecker {
|
||||
protected final TestCounter counter;
|
||||
|
||||
protected BaseChecker(final TestCounter counter) {
|
||||
this.counter = counter;
|
||||
}
|
||||
|
||||
public ExtendedRandom random() {
|
||||
return counter.random();
|
||||
}
|
||||
|
||||
public int mode() {
|
||||
return counter.mode();
|
||||
}
|
||||
}
|
||||
95
common/base/Either.java
Normal file
95
common/base/Either.java
Normal file
@@ -0,0 +1,95 @@
|
||||
package base;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public interface Either<L, R> {
|
||||
<NR> Either<L, NR> mapRight(final Function<? super R, NR> f);
|
||||
<NR> Either<L, NR> flatMapRight(final Function<? super R, ? extends Either<L, NR>> f);
|
||||
<T> T either(Function<? super L, ? extends T> lf, Function<? super R, ? extends T> rf);
|
||||
|
||||
boolean isRight();
|
||||
|
||||
L getLeft();
|
||||
R getRight();
|
||||
|
||||
static <L, R> Either<L, R> right(final R value) {
|
||||
return new Either<>() {
|
||||
@Override
|
||||
public <NR> Either<L, NR> mapRight(final Function<? super R, NR> f) {
|
||||
return right(f.apply(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <NR> Either<L, NR> flatMapRight(final Function<? super R, ? extends Either<L, NR>> f) {
|
||||
return f.apply(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T either(final Function<? super L, ? extends T> lf, final Function<? super R, ? extends T> rf) {
|
||||
return rf.apply(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRight() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public L getLeft() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R getRight() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Right(%s)", value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static <L, R> Either<L, R> left(final L value) {
|
||||
return new Either<>() {
|
||||
@Override
|
||||
public <NR> Either<L, NR> mapRight(final Function<? super R, NR> f) {
|
||||
return left(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <NR> Either<L, NR> flatMapRight(final Function<? super R, ? extends Either<L, NR>> f) {
|
||||
return left(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T either(final Function<? super L, ? extends T> lf, final Function<? super R, ? extends T> rf) {
|
||||
return lf.apply(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRight() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public L getLeft() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R getRight() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Left(%s)", value);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
89
common/base/ExtendedRandom.java
Normal file
89
common/base/ExtendedRandom.java
Normal file
@@ -0,0 +1,89 @@
|
||||
package base;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class ExtendedRandom {
|
||||
public static final String ENGLISH = "abcdefghijklmnopqrstuvwxyz";
|
||||
public static final String RUSSIAN = "абвгдеежзийклмнопрстуфхцчшщъыьэюя";
|
||||
public static final String GREEK = "αβγŋδεζηθικλμνξοπρτυφχψω";
|
||||
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
|
||||
public static final String SPACES = " \t\n\u000B\u2029\f";
|
||||
|
||||
private final Random random;
|
||||
|
||||
public ExtendedRandom(final Random random) {
|
||||
this.random = random;
|
||||
}
|
||||
|
||||
public ExtendedRandom(final Class<?> owner) {
|
||||
this(new Random(7912736473497634913L + owner.getName().hashCode()));
|
||||
}
|
||||
|
||||
public String randomString(final String chars) {
|
||||
return randomChar(chars) + (random.nextBoolean() ? "" : randomString(chars));
|
||||
}
|
||||
|
||||
public char randomChar(final String chars) {
|
||||
return chars.charAt(nextInt(chars.length()));
|
||||
}
|
||||
|
||||
public String randomString(final String chars, final int length) {
|
||||
final StringBuilder string = new StringBuilder();
|
||||
for (int i = 0; i < length; i++) {
|
||||
string.append(randomChar(chars));
|
||||
}
|
||||
return string.toString();
|
||||
}
|
||||
|
||||
public String randomString(final String chars, final int minLength, final int maxLength) {
|
||||
return randomString(chars, nextInt(minLength, maxLength));
|
||||
}
|
||||
|
||||
public boolean nextBoolean() {
|
||||
return random.nextBoolean();
|
||||
}
|
||||
|
||||
public int nextInt() {
|
||||
return random.nextInt();
|
||||
}
|
||||
|
||||
public int nextInt(final int min, final int max) {
|
||||
return nextInt(max - min + 1) + min;
|
||||
}
|
||||
|
||||
public int nextInt(final int n) {
|
||||
return random.nextInt(n);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public final <T> T randomItem(final T... items) {
|
||||
return items[nextInt(items.length)];
|
||||
}
|
||||
|
||||
public <T> T randomItem(final List<T> items) {
|
||||
return items.get(nextInt(items.size()));
|
||||
}
|
||||
|
||||
public Random getRandom() {
|
||||
return random;
|
||||
}
|
||||
|
||||
public <T> List<T> random(final int list, final Function<ExtendedRandom, T> generator) {
|
||||
return Stream.generate(() -> generator.apply(this)).limit(list).toList();
|
||||
}
|
||||
|
||||
public double nextDouble() {
|
||||
return random.nextDouble();
|
||||
}
|
||||
|
||||
public <E> void shuffle(final List<E> all) {
|
||||
Collections.shuffle(all, random);
|
||||
}
|
||||
}
|
||||
92
common/base/Functional.java
Normal file
92
common/base/Functional.java
Normal file
@@ -0,0 +1,92 @@
|
||||
package base;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class Functional {
|
||||
private Functional() {}
|
||||
|
||||
public static <T, R> List<R> map(final Collection<T> items, final Function<? super T, ? extends R> f) {
|
||||
return items.stream().map(f).collect(Collectors.toUnmodifiableList());
|
||||
}
|
||||
|
||||
public static <T, R> List<R> map(final List<T> items, final BiFunction<? super Integer, ? super T, ? extends R> f) {
|
||||
return IntStream.range(0, items.size())
|
||||
.mapToObj(i -> f.apply(i, items.get(i)))
|
||||
.collect(Collectors.toUnmodifiableList());
|
||||
}
|
||||
|
||||
public static <K, T, R> Map<K, R> mapValues(final Map<K, T> map, final Function<T, R> f) {
|
||||
return map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> f.apply(e.getValue())));
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <K, T> Map<K, T> mergeMaps(final Map<K, T>... maps) {
|
||||
return Stream.of(maps).flatMap(m -> m.entrySet().stream())
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a));
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> List<T> concat(final Collection<? extends T>... items) {
|
||||
final List<T> result = new ArrayList<>();
|
||||
for (final Collection<? extends T> item : items) {
|
||||
result.addAll(item);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> List<T> append(final Collection<T> collection, final T item) {
|
||||
final List<T> list = new ArrayList<>(collection);
|
||||
list.add(item);
|
||||
return list;
|
||||
}
|
||||
|
||||
public static <T> List<List<T>> allValues(final List<T> vals, final int length) {
|
||||
return Stream.generate(() -> vals)
|
||||
.limit(length)
|
||||
.reduce(
|
||||
List.of(List.of()),
|
||||
(prev, next) -> next.stream()
|
||||
.flatMap(value -> prev.stream().map(list -> append(list, value)))
|
||||
.toList(),
|
||||
(prev, next) -> next.stream()
|
||||
.flatMap(suffix -> prev.stream().map(prefix -> concat(prefix, suffix)))
|
||||
.toList()
|
||||
);
|
||||
}
|
||||
|
||||
public static <K, V> V get(final Map<K, V> map, final K key) {
|
||||
final V result = map.get(key);
|
||||
if (result == null) {
|
||||
throw new NullPointerException(key.toString() + " in " + map(map.keySet(), Objects::toString));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void addRange(final List<Integer> values, final int d, final int c) {
|
||||
for (int i = -d; i <= d; i++) {
|
||||
values.add(c + i);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> void forEachPair(final T[] items, final BiConsumer<? super T, ? super T> consumer) {
|
||||
assert items.length % 2 == 0;
|
||||
IntStream.range(0, items.length / 2).forEach(i -> consumer.accept(items[i * 2], items[i * 2 + 1]));
|
||||
}
|
||||
|
||||
|
||||
public static <T> List<Pair<T, T>> toPairs(final T[] items) {
|
||||
assert items.length % 2 == 0;
|
||||
return IntStream.range(0, items.length / 2)
|
||||
.mapToObj(i -> Pair.of(items[i * 2], items[i * 2 + 1]))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
56
common/base/Log.java
Normal file
56
common/base/Log.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package base;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class Log {
|
||||
private final Pattern LINES = Pattern.compile("\n");
|
||||
private int indent;
|
||||
|
||||
public static Supplier<Void> action(final Runnable action) {
|
||||
return () -> {
|
||||
action.run();
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
public void scope(final String name, final Runnable action) {
|
||||
scope(name, action(action));
|
||||
}
|
||||
|
||||
public <T> T scope(final String name, final Supplier<T> action) {
|
||||
println(name);
|
||||
indent++;
|
||||
try {
|
||||
return silentScope(name, action);
|
||||
} finally {
|
||||
indent--;
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T silentScope(final String ignoredName, final Supplier<T> action) {
|
||||
return action.get();
|
||||
}
|
||||
|
||||
@SuppressWarnings("UseOfSystemOutOrSystemErr")
|
||||
public void println(final Object value) {
|
||||
for (final String line : LINES.split(String.valueOf(value))) {
|
||||
System.out.println(indent() + line);
|
||||
}
|
||||
}
|
||||
|
||||
public void format(final String format, final Object... args) {
|
||||
println(String.format(format,args));
|
||||
}
|
||||
|
||||
private String indent() {
|
||||
return " ".repeat(indent);
|
||||
}
|
||||
|
||||
protected int getIndent() {
|
||||
return indent;
|
||||
}
|
||||
}
|
||||
28
common/base/MainChecker.java
Normal file
28
common/base/MainChecker.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package base;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
|
||||
public final class MainChecker {
|
||||
private final Runner runner;
|
||||
|
||||
public MainChecker(final Runner runner) {
|
||||
this.runner = runner;
|
||||
}
|
||||
|
||||
public List<String> run(final TestCounter counter, final String... input) {
|
||||
return runner.run(counter, input);
|
||||
}
|
||||
|
||||
public List<String> run(final TestCounter counter, final List<String> input) {
|
||||
return runner.run(counter, input);
|
||||
}
|
||||
|
||||
public void testEquals(final TestCounter counter, final List<String> input, final List<String> expected) {
|
||||
runner.testEquals(counter, input, expected);
|
||||
}
|
||||
|
||||
}
|
||||
15
common/base/Named.java
Normal file
15
common/base/Named.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package base;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public record Named<T>(String name, T value) {
|
||||
public static <T> Named<T> of(final String name, final T f) {
|
||||
return new Named<>(name, f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
44
common/base/Pair.java
Normal file
44
common/base/Pair.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package base;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.BinaryOperator;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
@SuppressWarnings({"StaticMethodOnlyUsedInOneClass", "unused"})
|
||||
public record Pair<F, S>(F first, S second) {
|
||||
public static <F, S> Pair<F, S> of(final F first, final S second) {
|
||||
return new Pair<>(first, second);
|
||||
}
|
||||
|
||||
public static <F, S> Pair<F, S> of(final Map.Entry<F, S> e) {
|
||||
return of(e.getKey(), e.getValue());
|
||||
}
|
||||
|
||||
public static <F, S> UnaryOperator<Pair<F, S>> lift(final UnaryOperator<F> f, final UnaryOperator<S> s) {
|
||||
return p -> of(f.apply(p.first), s.apply(p.second));
|
||||
}
|
||||
|
||||
public static <F, S> BinaryOperator<Pair<F, S>> lift(final BinaryOperator<F> f, final BinaryOperator<S> s) {
|
||||
return (p1, p2) -> of(f.apply(p1.first, p2.first), s.apply(p1.second, p2.second));
|
||||
}
|
||||
|
||||
public static <T, F, S> Function<T, Pair<F, S>> tee(
|
||||
final Function<? super T, ? extends F> f,
|
||||
final Function<? super T, ? extends S> s
|
||||
) {
|
||||
return t -> of(f.apply(t), s.apply(t));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "(" + first + ", " + second + ")";
|
||||
}
|
||||
|
||||
public <R> Pair<F, R> second(final R second) {
|
||||
return new Pair<>(first, second);
|
||||
}
|
||||
}
|
||||
185
common/base/Runner.java
Normal file
185
common/base/Runner.java
Normal file
@@ -0,0 +1,185 @@
|
||||
package base;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@FunctionalInterface
|
||||
public interface Runner {
|
||||
List<String> run(final TestCounter counter, final List<String> input);
|
||||
|
||||
default List<String> run(final TestCounter counter, final String... input) {
|
||||
return run(counter, List.of(input));
|
||||
}
|
||||
|
||||
default void testEquals(final TestCounter counter, final List<String> input, final List<String> expected) {
|
||||
counter.test(() -> {
|
||||
final List<String> actual = run(counter, input);
|
||||
for (int i = 0; i < Math.min(expected.size(), actual.size()); i++) {
|
||||
final String exp = expected.get(i);
|
||||
final String act = actual.get(i);
|
||||
if (!exp.equalsIgnoreCase(act)) {
|
||||
Asserts.assertEquals("Line " + (i + 1), exp, act);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Asserts.assertEquals("Number of lines", expected.size(), actual.size());
|
||||
});
|
||||
}
|
||||
|
||||
static Packages packages(final String... packages) {
|
||||
return new Packages(List.of(packages));
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface CommentRunner {
|
||||
List<String> run(String comment, TestCounter counter, List<String> input);
|
||||
}
|
||||
|
||||
final class Packages {
|
||||
private final List<String> packages;
|
||||
|
||||
private Packages(final List<String> packages) {
|
||||
this.packages = packages;
|
||||
}
|
||||
|
||||
public Runner std(final String className) {
|
||||
final CommentRunner main = main(className);
|
||||
return (counter, input) -> counter.call("io", () -> {
|
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try (final PrintWriter writer = new PrintWriter(baos)) {
|
||||
input.forEach(writer::println);
|
||||
}
|
||||
|
||||
final InputStream oldIn = System.in;
|
||||
try {
|
||||
System.setIn(new ByteArrayInputStream(baos.toByteArray()));
|
||||
return main.run(String.format("[%d input lines]", input.size()), counter, List.of());
|
||||
} finally {
|
||||
System.setIn(oldIn);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConfusingMainMethod")
|
||||
public CommentRunner main(final String className) {
|
||||
final Method method = findMain(className);
|
||||
|
||||
return (comment, counter, input) -> {
|
||||
counter.format("Running test %02d: java %s %s%n", counter.getTestNo(), method.getDeclaringClass().getName(), comment);
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
@SuppressWarnings("UseOfSystemOutOrSystemErr") final PrintStream oldOut = System.out;
|
||||
try {
|
||||
System.setOut(new PrintStream(out, false, StandardCharsets.UTF_8));
|
||||
method.invoke(null, new Object[]{input.toArray(String[]::new)});
|
||||
final BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(out.toByteArray()), StandardCharsets.UTF_8));
|
||||
final List<String> result = new ArrayList<>();
|
||||
while (true) {
|
||||
final String line = reader.readLine();
|
||||
if (line == null) {
|
||||
if (result.isEmpty()) {
|
||||
result.add("");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
result.add(line.trim());
|
||||
}
|
||||
} catch (final InvocationTargetException e) {
|
||||
final Throwable cause = e.getCause();
|
||||
throw Asserts.error("main thrown exception %s: %s", cause.getClass().getSimpleName(), cause);
|
||||
} catch (final Exception e) {
|
||||
throw Asserts.error("Cannot invoke main: %s: %s", e.getClass().getSimpleName(), e.getMessage());
|
||||
} finally {
|
||||
System.setOut(oldOut);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Method findMain(final String className) {
|
||||
try {
|
||||
final URL url = new File(".").toURI().toURL();
|
||||
final List<String> candidates = packages.stream()
|
||||
.flatMap(pkg -> {
|
||||
final String prefix = pkg.isEmpty() ? pkg : pkg + ".";
|
||||
return Stream.of(prefix + className, prefix + "$" + className);
|
||||
})
|
||||
.toList();
|
||||
|
||||
//noinspection ClassLoaderInstantiation,resource,IOResourceOpenedButNotSafelyClosed
|
||||
final URLClassLoader classLoader = new URLClassLoader(new URL[]{url});
|
||||
for (final String candidate : candidates) {
|
||||
try {
|
||||
final Class<?> loaded = classLoader.loadClass(candidate);
|
||||
if (!Modifier.isPublic(loaded.getModifiers())) {
|
||||
throw Asserts.error("Class %s is not public", candidate);
|
||||
}
|
||||
final Method main = loaded.getMethod("main", String[].class);
|
||||
if (!Modifier.isPublic(main.getModifiers()) || !Modifier.isStatic(main.getModifiers())) {
|
||||
throw Asserts.error("Method main of class %s should be public and static", candidate);
|
||||
}
|
||||
return main;
|
||||
} catch (final ClassNotFoundException e) {
|
||||
// Ignore
|
||||
} catch (final NoSuchMethodException e) {
|
||||
throw Asserts.error("Could not find method main(String[]) in class %s", candidate, e);
|
||||
}
|
||||
}
|
||||
throw Asserts.error("Could not find neither of classes %s", candidates);
|
||||
} catch (final MalformedURLException e) {
|
||||
throw Asserts.error("Invalid path", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getClassName(final String pkg, final String className) {
|
||||
return pkg.isEmpty() ? className : pkg + "." + className;
|
||||
}
|
||||
|
||||
public Runner args(final String className) {
|
||||
final CommentRunner main = main(className);
|
||||
// final AtomicReference<String> prev = new AtomicReference<>("");
|
||||
return (counter, input) -> {
|
||||
final int total = input.stream().mapToInt(String::length).sum() + input.size() * 3;
|
||||
final String comment = total <= 300
|
||||
? input.stream().collect(Collectors.joining("\" \"", "\"", "\""))
|
||||
: input.size() <= 100
|
||||
? String.format("[%d arguments, sizes: %s]", input.size(), input.stream()
|
||||
.mapToInt(String::length)
|
||||
.mapToObj(String::valueOf)
|
||||
.collect(Collectors.joining(", ")))
|
||||
: String.format("[%d arguments, total size: %d]", input.size(), total);
|
||||
// assert comment.length() <= 5 || !prev.get().equals(comment) : "Duplicate tests " + comment;
|
||||
// prev.set(comment);
|
||||
return main.run(comment, counter, input);
|
||||
};
|
||||
}
|
||||
|
||||
public Runner files(final String className) {
|
||||
final Runner args = args(className);
|
||||
return (counter, input) -> counter.call("io", () -> {
|
||||
final Path inf = counter.getFile("in");
|
||||
final Path ouf = counter.getFile("out");
|
||||
Files.write(inf, input);
|
||||
args.run(counter, List.of(inf.toString(), ouf.toString()));
|
||||
final List<String> output = Files.readAllLines(ouf);
|
||||
Files.delete(inf);
|
||||
Files.delete(ouf);
|
||||
return output;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
143
common/base/Selector.java
Normal file
143
common/base/Selector.java
Normal file
@@ -0,0 +1,143 @@
|
||||
package base;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class Selector {
|
||||
private final Class<?> owner;
|
||||
private final List<String> modes;
|
||||
private final Set<String> variantNames = new LinkedHashSet<>();
|
||||
private final Map<String, Consumer<TestCounter>> variants = new LinkedHashMap<>();
|
||||
|
||||
public Selector(final Class<?> owner, final String... modes) {
|
||||
this.owner = owner;
|
||||
this.modes = List.of(modes);
|
||||
}
|
||||
|
||||
public Selector variant(final String name, final Consumer<TestCounter> operations) {
|
||||
Asserts.assertTrue("Duplicate variant " + name, variants.put(name.toLowerCase(), operations) == null);
|
||||
variantNames.add(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
private static void check(final boolean condition, final String format, final Object... args) {
|
||||
if (!condition) {
|
||||
throw new IllegalArgumentException(String.format(format, args));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("UseOfSystemOutOrSystemErr")
|
||||
public void main(final String... args) {
|
||||
try {
|
||||
final String mode;
|
||||
if (modes.isEmpty()) {
|
||||
check(args.length >= 1, "At least one argument expected, found %s", args.length);
|
||||
mode = "";
|
||||
} else {
|
||||
check(args.length >= 2, "At least two arguments expected, found %s", args.length);
|
||||
mode = args[0];
|
||||
}
|
||||
|
||||
final List<String> vars = Arrays.stream(args).skip(modes.isEmpty() ? 0 : 1)
|
||||
.flatMap(arg -> Arrays.stream(arg.split("[ +]+")))
|
||||
.toList();
|
||||
|
||||
test(mode, vars);
|
||||
} catch (final IllegalArgumentException e) {
|
||||
System.err.println("ERROR: " + e.getMessage());
|
||||
if (modes.isEmpty()) {
|
||||
System.err.println("Usage: " + owner.getName() + " VARIANT...");
|
||||
} else {
|
||||
System.err.println("Usage: " + owner.getName() + " MODE VARIANT...");
|
||||
System.err.println("Modes: " + String.join(", ", modes));
|
||||
}
|
||||
System.err.println("Variants: " + String.join(", ", variantNames));
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
public void test(final String mode, List<String> vars) {
|
||||
final int modeNo = modes.isEmpty() ? -1 : modes.indexOf(mode) ;
|
||||
check(modes.isEmpty() || modeNo >= 0, "Unknown mode '%s'", mode);
|
||||
if (variantNames.contains("Base") && !vars.contains("Base")) {
|
||||
vars = new ArrayList<>(vars);
|
||||
vars.add(0, "Base");
|
||||
}
|
||||
|
||||
vars.forEach(variant -> check(variants.containsKey(variant.toLowerCase()), "Unknown variant '%s'", variant));
|
||||
|
||||
final Map<String, String> properties = modes.isEmpty()
|
||||
? Map.of("variant", String.join("+", vars))
|
||||
: Map.of("variant", String.join("+", vars), "mode", mode);
|
||||
final TestCounter counter = new TestCounter(owner, modeNo, properties);
|
||||
counter.printHead();
|
||||
vars.forEach(variant -> counter.scope("Testing " + variant, () -> variants.get(variant.toLowerCase()).accept(counter)));
|
||||
counter.printStatus();
|
||||
}
|
||||
|
||||
public static <V extends Tester> Composite<V> composite(final Class<?> owner, final Function<TestCounter, V> factory, final String... modes) {
|
||||
return new Composite<>(owner, factory, (tester, counter) -> tester.test(), modes);
|
||||
}
|
||||
|
||||
public static <V> Composite<V> composite(final Class<?> owner, final Function<TestCounter, V> factory, final BiConsumer<V, TestCounter> tester, final String... modes) {
|
||||
return new Composite<>(owner, factory, tester, modes);
|
||||
}
|
||||
|
||||
public List<String> getModes() {
|
||||
return modes.isEmpty() ? List.of("~") : modes;
|
||||
}
|
||||
|
||||
public List<String> getVariants() {
|
||||
return List.copyOf(variants.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public static final class Composite<V> {
|
||||
private final Selector selector;
|
||||
private final Function<TestCounter, V> factory;
|
||||
private final BiConsumer<V, TestCounter> tester;
|
||||
private List<Consumer<? super V>> base;
|
||||
|
||||
private Composite(final Class<?> owner, final Function<TestCounter, V> factory, final BiConsumer<V, TestCounter> tester, final String... modes) {
|
||||
selector = new Selector(owner, modes);
|
||||
this.factory = factory;
|
||||
this.tester = tester;
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public final Composite<V> variant(final String name, final Consumer<? super V>... parts) {
|
||||
if ("Base".equalsIgnoreCase(name)) {
|
||||
base = List.of(parts);
|
||||
return v(name.toLowerCase());
|
||||
} else {
|
||||
return v(name, parts);
|
||||
}
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
private Composite<V> v(final String name, final Consumer<? super V>... parts) {
|
||||
selector.variant(name, counter -> {
|
||||
final V variant = factory.apply(counter);
|
||||
for (final Consumer<? super V> part : base) {
|
||||
part.accept(variant);
|
||||
}
|
||||
for (final Consumer<? super V> part : parts) {
|
||||
part.accept(variant);
|
||||
}
|
||||
tester.accept(variant, counter);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
public Selector selector() {
|
||||
return selector;
|
||||
}
|
||||
}
|
||||
}
|
||||
184
common/base/TestCounter.java
Normal file
184
common/base/TestCounter.java
Normal file
@@ -0,0 +1,184 @@
|
||||
package base;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class TestCounter extends Log {
|
||||
public static final int DENOMINATOR = Integer.getInteger("base.denominator", 1);
|
||||
public static final int DENOMINATOR2 = (int) Math.round(Math.sqrt(DENOMINATOR));
|
||||
|
||||
private static final String JAR_EXT = ".jar";
|
||||
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
|
||||
|
||||
private final Class<?> owner;
|
||||
private final int mode;
|
||||
private final Map<String, ?> properties;
|
||||
private final ExtendedRandom random;
|
||||
|
||||
private final long start = System.currentTimeMillis();
|
||||
private int passed;
|
||||
|
||||
public TestCounter(final Class<?> owner, final int mode, final Map<String, ?> properties) {
|
||||
Locale.setDefault(Locale.US);
|
||||
Asserts.checkAssert(getClass());
|
||||
|
||||
this.owner = owner;
|
||||
this.mode = mode;
|
||||
this.properties = properties;
|
||||
random = new ExtendedRandom(owner);
|
||||
}
|
||||
|
||||
public int mode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public int getTestNo() {
|
||||
return passed + 1;
|
||||
}
|
||||
|
||||
public void test(final Runnable action) {
|
||||
testV(() -> {
|
||||
action.run();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public <T> void testForEach(final Iterable<? extends T> items, final Consumer<? super T> action) {
|
||||
for (final T item : items) {
|
||||
test(() -> action.accept(item));
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T testV(final Supplier<T> action) {
|
||||
return silentScope("Test " + getTestNo(), () -> {
|
||||
final T result = action.get();
|
||||
passed++;
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
private String getLine() {
|
||||
return getIndent() == 0 ? "=" : "-";
|
||||
}
|
||||
|
||||
public void printHead() {
|
||||
println("=== " + getTitle());
|
||||
}
|
||||
|
||||
public void printStatus() {
|
||||
format("%s%n%s%n", getLine().repeat(30), getTitle());
|
||||
format("%d tests passed in %dms%n", passed, System.currentTimeMillis() - start);
|
||||
println("Version: " + getVersion(owner));
|
||||
println("");
|
||||
}
|
||||
|
||||
private String getTitle() {
|
||||
return String.format("%s %s", owner.getSimpleName(), properties.isEmpty() ? "" : properties);
|
||||
}
|
||||
|
||||
private static String getVersion(final Class<?> clazz) {
|
||||
try {
|
||||
final ClassLoader cl = clazz.getClassLoader();
|
||||
final URL url = cl.getResource(clazz.getName().replace('.', '/') + ".class");
|
||||
if (url == null) {
|
||||
return "(no manifest)";
|
||||
}
|
||||
|
||||
final String path = url.getPath();
|
||||
final int index = path.indexOf(JAR_EXT);
|
||||
if (index == -1) {
|
||||
return DATE_FORMAT.format(new Date(new File(path).lastModified()));
|
||||
}
|
||||
|
||||
final String jarPath = path.substring(0, index + JAR_EXT.length());
|
||||
try (final JarFile jarFile = new JarFile(new File(new URI(jarPath)))) {
|
||||
final JarEntry entry = jarFile.getJarEntry("META-INF/MANIFEST.MF");
|
||||
return DATE_FORMAT.format(new Date(entry.getTime()));
|
||||
}
|
||||
} catch (final IOException | URISyntaxException e) {
|
||||
return "error: " + e;
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T call(final String message, final SupplierE<T> supplier) {
|
||||
return get(supplier).either(e -> fail(e, "%s", message), Function.identity());
|
||||
}
|
||||
|
||||
public void shouldFail(final String message, @SuppressWarnings("TypeMayBeWeakened") final RunnableE action) {
|
||||
test(() -> get(action).either(e -> null, v -> fail("%s", message)));
|
||||
}
|
||||
|
||||
public <T> T fail(final String format, final Object... args) {
|
||||
return fail(Asserts.error(format, args));
|
||||
}
|
||||
|
||||
public <T> T fail(final Throwable throwable) {
|
||||
return fail(throwable, "%s: %s", throwable.getClass().getSimpleName(), throwable.getMessage());
|
||||
}
|
||||
|
||||
public <T> T fail(final Throwable throwable, final String format, final Object... args) {
|
||||
final String message = String.format(format, args);
|
||||
println("ERROR: " + message);
|
||||
throw throwable instanceof Error ? (Error) throwable : new AssertionError(throwable);
|
||||
}
|
||||
|
||||
public void checkTrue(final boolean condition, final String message, final Object... args) {
|
||||
if (!condition) {
|
||||
fail(message, args);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> Either<Exception, T> get(final SupplierE<T> supplier) {
|
||||
return supplier.get();
|
||||
}
|
||||
|
||||
public Path getFile(final String suffix) {
|
||||
return Paths.get(String.format("test%d.%s", getTestNo(), suffix));
|
||||
}
|
||||
|
||||
public ExtendedRandom random() {
|
||||
return random;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface SupplierE<T> extends Supplier<Either<Exception, T>> {
|
||||
T getE() throws Exception;
|
||||
|
||||
@Override
|
||||
default Either<Exception, T> get() {
|
||||
try {
|
||||
return Either.right(getE());
|
||||
} catch (final Exception e) {
|
||||
return Either.left(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface RunnableE extends SupplierE<Void> {
|
||||
void run() throws Exception;
|
||||
|
||||
@Override
|
||||
default Void getE() throws Exception {
|
||||
run();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
common/base/Tester.java
Normal file
18
common/base/Tester.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package base;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public abstract class Tester extends BaseChecker {
|
||||
protected Tester(final TestCounter counter) {
|
||||
super(counter);
|
||||
}
|
||||
|
||||
public abstract void test();
|
||||
|
||||
public void run(final Class<?> test, final String... args) {
|
||||
System.out.println("=== Testing " + test.getSimpleName() + " " + String.join(" ", args));
|
||||
test();
|
||||
counter.printStatus();
|
||||
}
|
||||
}
|
||||
15
common/base/Unit.java
Normal file
15
common/base/Unit.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package base;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class Unit {
|
||||
public static final Unit INSTANCE = new Unit();
|
||||
|
||||
private Unit() { }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "unit";
|
||||
}
|
||||
}
|
||||
7
common/base/package-info.java
Normal file
7
common/base/package-info.java
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Common homeworks test classes
|
||||
* of <a href="https://www.kgeorgiy.info/courses/paradigms/">Paradigms of Programming</a> course.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
package base;
|
||||
37
common/common/Engine.java
Normal file
37
common/common/Engine.java
Normal file
@@ -0,0 +1,37 @@
|
||||
package common;
|
||||
|
||||
import base.Asserts;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* Test engine.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public interface Engine<X> {
|
||||
Result<X> prepare(String expression);
|
||||
|
||||
Result<Number> evaluate(final Result<X> prepared, double[] vars);
|
||||
|
||||
Result<String> toString(final Result<X> prepared);
|
||||
|
||||
default Result<X> parse(final String expression) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
record Result<T>(String context, T value) {
|
||||
public void assertEquals(final T expected) {
|
||||
Asserts.assertEquals(context(), expected, value());
|
||||
}
|
||||
|
||||
public <R> Result<R> cast(final BiFunction<T, String, R> convert) {
|
||||
return new Result<>(context(), convert.apply(value(), context()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return context();
|
||||
}
|
||||
}
|
||||
}
|
||||
12
common/common/EngineException.java
Normal file
12
common/common/EngineException.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package common;
|
||||
|
||||
/**
|
||||
* Thrown on test engine error.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class EngineException extends RuntimeException {
|
||||
public EngineException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
13
common/common/expression/AnyOp.java
Normal file
13
common/common/expression/AnyOp.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package common.expression;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public record AnyOp(ExprTester.Func f, int min, int max, int fixed) {
|
||||
public Predicate<Collection<?>> arity() {
|
||||
return args -> min <= args.size() && args.size() <= max;
|
||||
}
|
||||
}
|
||||
271
common/common/expression/ArithmeticBuilder.java
Normal file
271
common/common/expression/ArithmeticBuilder.java
Normal file
@@ -0,0 +1,271 @@
|
||||
package common.expression;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.DoubleBinaryOperator;
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Basic arithmetics.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class ArithmeticBuilder implements OperationsBuilder {
|
||||
private final BaseVariant variant;
|
||||
private final F neg;
|
||||
private final F add;
|
||||
private final F sub;
|
||||
private final F mul;
|
||||
private final F div;
|
||||
|
||||
public ArithmeticBuilder(final boolean varargs, final List<String> variables) {
|
||||
variant = new BaseVariant(varargs);
|
||||
variables.forEach(this::variable);
|
||||
|
||||
//noinspection Convert2MethodRef
|
||||
variant.infix("+", 100, (a, b) -> a + b);
|
||||
variant.infix("-", 100, (a, b) -> a - b);
|
||||
variant.infix("*", 200, (a, b) -> a * b);
|
||||
variant.infix("/", 200, (a, b) -> a / b);
|
||||
variant.unary("negate", a -> -a);
|
||||
|
||||
add = f("+", 2);
|
||||
sub = f("-", 2);
|
||||
mul = f("*", 2);
|
||||
div = f("/", 2);
|
||||
neg = f("negate", 1);
|
||||
|
||||
basicTests();
|
||||
}
|
||||
|
||||
public void basicTests() {
|
||||
final List<F> ops = List.of(neg, add, sub, mul, div);
|
||||
variant.tests(() -> Stream.of(
|
||||
Stream.of(variant.c()),
|
||||
variant.getVariables().stream(),
|
||||
ops.stream().map(F::c),
|
||||
ops.stream().map(F::v),
|
||||
ops.stream().map(F::r),
|
||||
ops.stream().map(F::r),
|
||||
Stream.of(
|
||||
div.f(neg.r(), r()),
|
||||
div.f(r(), mul.r()),
|
||||
add.f(add.f(mul.r(), mul.r()), mul.r()),
|
||||
sub.f(add.f(mul.r(), mul.f(r(), mul.f(r(), mul.r()))), mul.r())
|
||||
)
|
||||
).flatMap(Function.identity()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void constant(final String name, final String alias, final double value) {
|
||||
alias(name, alias);
|
||||
final ExprTester.Func expr = vars -> value;
|
||||
variant.nullary(name, expr);
|
||||
final Expr constant = Expr.nullary(name, expr);
|
||||
variant.tests(() -> Stream.of(
|
||||
neg.f(constant),
|
||||
add.f(constant, r()),
|
||||
sub.f(r(), constant),
|
||||
mul.f(r(), constant),
|
||||
div.f(constant, r())
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unary(final String name, final String alias, final DoubleUnaryOperator op) {
|
||||
variant.unary(name, op);
|
||||
variant.alias(name, alias);
|
||||
unaryTests(name);
|
||||
}
|
||||
|
||||
private void unaryTests(final String name) {
|
||||
final F op = f(name, 1);
|
||||
variant.tests(() -> Stream.of(
|
||||
op.c(),
|
||||
op.v(),
|
||||
op.f(sub.r()),
|
||||
op.f(add.r()),
|
||||
op.f(div.f(op.r(), add.r())),
|
||||
add.f(op.f(op.f(add.r())), mul.f(r(), mul.f(r(), op.r())))
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binary(final String name, final String alias, final DoubleBinaryOperator op) {
|
||||
variant.binary(name, op);
|
||||
variant.alias(name, alias);
|
||||
binaryTests(name);
|
||||
}
|
||||
|
||||
private void binaryTests(final String name) {
|
||||
final F op = f(name, 2);
|
||||
variant.tests(() -> Stream.of(
|
||||
op.c(),
|
||||
op.v(),
|
||||
op.r(),
|
||||
op.f(neg.r(), add.r()),
|
||||
op.f(sub.r(), neg.r()),
|
||||
op.f(neg.r(), op.r()),
|
||||
op.f(op.r(), neg.r())
|
||||
));
|
||||
}
|
||||
|
||||
private record F(String name, int arity, BaseVariant variant) {
|
||||
public Expr f(final Expr... args) {
|
||||
assert arity < 0 || arity == args.length;
|
||||
return variant.f(name, args);
|
||||
}
|
||||
|
||||
public Expr v() {
|
||||
return g(variant::v);
|
||||
}
|
||||
|
||||
public Expr c() {
|
||||
return g(variant::c);
|
||||
}
|
||||
|
||||
public Expr r() {
|
||||
return g(variant::r);
|
||||
}
|
||||
|
||||
private Expr g(final Supplier<Expr> g) {
|
||||
return f(Stream.generate(g).limit(arity).toArray(Expr[]::new));
|
||||
}
|
||||
}
|
||||
|
||||
private F f(final String name, final int arity) {
|
||||
return new F(name, arity, variant);
|
||||
}
|
||||
|
||||
private Expr r() {
|
||||
return variant.r();
|
||||
}
|
||||
|
||||
private Expr f(final String name, final Expr... args) {
|
||||
return variant.f(name, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void infix(final String name, final String alias, final int priority, final DoubleBinaryOperator op) {
|
||||
variant.infix(name, priority, op);
|
||||
variant.alias(name, alias);
|
||||
binaryTests(name);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void fixed(
|
||||
final String name,
|
||||
final String alias,
|
||||
final int arity,
|
||||
final ExprTester.Func f
|
||||
) {
|
||||
variant.fixed(name, arity, f);
|
||||
variant.alias(name, alias);
|
||||
|
||||
if (arity == 1) {
|
||||
unaryTests(name);
|
||||
} else if (arity == 2) {
|
||||
binaryTests(name);
|
||||
} else if (arity == 3) {
|
||||
final F op = f(name, 3);
|
||||
variant.tests(() -> {
|
||||
final Expr e1 = op.c();
|
||||
final Expr e2 = op.v();
|
||||
final Expr e3 = op.f(add.r(), sub.r(), mul.r());
|
||||
return Stream.of(
|
||||
op.f(variant.c(), r(), r()),
|
||||
op.f(r(), variant.c(), r()),
|
||||
op.f(r(), r(), variant.c()),
|
||||
op.f(variant.v(), mul.v(), mul.v()),
|
||||
op.f(mul.v(), variant.v(), mul.v()),
|
||||
op.f(mul.v(), r(), mul.v()),
|
||||
op.r(),
|
||||
e1,
|
||||
e2,
|
||||
e3,
|
||||
op.f(e1, e2, e3)
|
||||
);
|
||||
});
|
||||
} else if (arity == 4) {
|
||||
final F op = f(name, 4);
|
||||
variant.tests(() -> {
|
||||
final Expr e1 = op.c();
|
||||
final Expr e2 = op.v();
|
||||
final Expr e3 = op.r();
|
||||
final Expr e4 = op.f(add.r(), sub.r(), mul.r(), div.r());
|
||||
return Stream.of(
|
||||
op.r(),
|
||||
op.r(),
|
||||
op.r(),
|
||||
e1,
|
||||
e2,
|
||||
e3,
|
||||
e4,
|
||||
op.f(e1, e2, e3, e4)
|
||||
);
|
||||
});
|
||||
} else {
|
||||
variant.tests(() -> Stream.concat(
|
||||
Stream.of(
|
||||
f(name, arity, variant::c),
|
||||
f(name, arity, variant::v)
|
||||
),
|
||||
IntStream.range(0, 10).mapToObj(i -> f(name, arity, variant::r))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private Expr f(final String name, final int arity, final Supplier<Expr> generator) {
|
||||
return f(name, Stream.generate(generator).limit(arity).toArray(Expr[]::new));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void any(
|
||||
final String name,
|
||||
final String alias,
|
||||
final AnyOp op
|
||||
) {
|
||||
variant.any(name, op);
|
||||
variant.alias(name, alias);
|
||||
|
||||
if (variant.hasVarargs()) {
|
||||
variant.tests(() -> Stream.<List<Expr>>of(
|
||||
List.of(),
|
||||
List.of(r()),
|
||||
List.of(r(), r()),
|
||||
List.of(r(), r(), r()),
|
||||
List.of(r(), r(), r(), r()),
|
||||
List.of(r(), r(), r(), r(), r()),
|
||||
List.of(add.r(), r()),
|
||||
List.of(r(), r(), sub.r())
|
||||
).filter(op.arity()).map(args -> args.toArray(Expr[]::new)).map(f(name, -1)::f));
|
||||
}
|
||||
|
||||
variant.tests(() -> IntStream.rangeClosed(op.min(), op.max())
|
||||
.mapToObj(i -> f(name, variant.hasVarargs() ? i : op.fixed(), variant::r)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void variable(final String name) {
|
||||
variant.variable(name, variant.getVariables().size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void alias(final String name, final String alias) {
|
||||
variant.alias(name, alias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(final String... names) {
|
||||
variant.remove(names);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseVariant variant() {
|
||||
return variant;
|
||||
}
|
||||
}
|
||||
211
common/common/expression/BaseVariant.java
Normal file
211
common/common/expression/BaseVariant.java
Normal file
@@ -0,0 +1,211 @@
|
||||
package common.expression;
|
||||
|
||||
import base.ExtendedRandom;
|
||||
import common.expression.ExprTester.Func;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.DoubleBinaryOperator;
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Base expressions variant.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class BaseVariant implements Variant {
|
||||
private static final int MAX_C = 1_000;
|
||||
private static final Expr ZERO = c(0);
|
||||
|
||||
private final ExtendedRandom random = new ExtendedRandom(getClass());
|
||||
private final boolean varargs;
|
||||
|
||||
private final StringMap<Operator> operators = new StringMap<>();
|
||||
private final StringMap<Expr> nullary = new StringMap<>();
|
||||
private final StringMap<Expr> variables = new StringMap<>();
|
||||
private final Map<String, String> aliases = new HashMap<>();
|
||||
|
||||
private final Map<String, Integer> priorities = new HashMap<>();
|
||||
|
||||
public final List<Supplier<Stream<Expr>>> tests = new ArrayList<>();
|
||||
|
||||
public BaseVariant(final boolean varargs) {
|
||||
this.varargs = varargs;
|
||||
}
|
||||
|
||||
public List<Expr> getTests() {
|
||||
return tests.stream().flatMap(Supplier::get).toList();
|
||||
}
|
||||
|
||||
public Expr randomTest(final int size) {
|
||||
return generate(size / 10 + 2);
|
||||
}
|
||||
|
||||
private Expr generate(final int depth) {
|
||||
return depth > 0 ? generateOp(depth) : r();
|
||||
}
|
||||
|
||||
public Expr r() {
|
||||
if (random.nextBoolean()) {
|
||||
return variables.random(random);
|
||||
} else if (nullary.isEmpty() || random.nextBoolean()){
|
||||
return c();
|
||||
} else {
|
||||
return nullary.random(random);
|
||||
}
|
||||
}
|
||||
|
||||
public Expr c() {
|
||||
return random.nextBoolean() ? ZERO : c(random.nextInt(-MAX_C, MAX_C));
|
||||
}
|
||||
|
||||
public Expr v() {
|
||||
return random().randomItem(variables.values().toArray(Expr[]::new));
|
||||
}
|
||||
|
||||
protected Expr generateOp(final int depth) {
|
||||
if (random.nextInt(6) == 0 || operators.isEmpty()) {
|
||||
return generateP(depth);
|
||||
} else {
|
||||
final Operator operator = operators.random(random);
|
||||
final Expr[] args = Stream.generate(() -> generateP(depth))
|
||||
.limit(random.nextInt(operator.minArity, operator.maxArity))
|
||||
.toArray(Expr[]::new);
|
||||
return f(operator.name, args);
|
||||
}
|
||||
}
|
||||
|
||||
protected Expr generateP(final int depth) {
|
||||
return generate(random.nextInt(depth));
|
||||
}
|
||||
|
||||
public void tests(final Supplier<Stream<Expr>> tests) {
|
||||
this.tests.add(tests);
|
||||
}
|
||||
|
||||
public void fixed(final String name, final int arity, final Func f) {
|
||||
op(name, arity, arity, f);
|
||||
}
|
||||
|
||||
public void op(final String name, final int minArity, final int maxArity, final Func f) {
|
||||
assert !operators.contains(name) : "Duplicate op %s".formatted(name);
|
||||
operators.put(name, new Operator(name, minArity, maxArity, f));
|
||||
}
|
||||
|
||||
public void remove(final String... names) {
|
||||
//noinspection SlowAbstractSetRemoveAll
|
||||
operators.values.keySet().removeAll(Arrays.asList(names));
|
||||
}
|
||||
|
||||
public void any(final String name, final AnyOp op) {
|
||||
if (varargs) {
|
||||
op(name, op.min(), op.max(), op.f());
|
||||
} else {
|
||||
op(name, op.fixed(), op.fixed(), op.f());
|
||||
}
|
||||
}
|
||||
|
||||
public void unary(final String name, final DoubleUnaryOperator answer) {
|
||||
fixed(name, 1, args -> answer.applyAsDouble(args[0]));
|
||||
}
|
||||
|
||||
public void binary(final String name, final DoubleBinaryOperator answer) {
|
||||
fixed(name, 2, args -> answer.applyAsDouble(args[0], args[1]));
|
||||
}
|
||||
|
||||
public void infix(final String name, final int priority, final DoubleBinaryOperator answer) {
|
||||
binary(name, answer);
|
||||
priorities.put(name, priority);
|
||||
}
|
||||
|
||||
public void nullary(final String name, final Func f) {
|
||||
nullary.put(name, Expr.nullary(name, f));
|
||||
}
|
||||
|
||||
public Expr f(final String name, final Expr... args) {
|
||||
return Expr.f(name, operators.get(name), List.of(args));
|
||||
}
|
||||
|
||||
protected Expr n(final String name) {
|
||||
return nullary.get(name);
|
||||
}
|
||||
|
||||
public static Expr c(final int value) {
|
||||
return Expr.constant(value);
|
||||
}
|
||||
|
||||
public Expr variable(final String name, final int index) {
|
||||
final Expr variable = Expr.variable(name, index);
|
||||
variables.put(name, variable);
|
||||
return variable;
|
||||
}
|
||||
|
||||
public List<Expr> getVariables() {
|
||||
return List.copyOf(variables.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtendedRandom random() {
|
||||
return random;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasVarargs() {
|
||||
return varargs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getPriority(final String op) {
|
||||
return priorities.get(op);
|
||||
}
|
||||
|
||||
private record Operator(String name, int minArity, int maxArity, Func f) implements Func {
|
||||
private Operator {
|
||||
assert 0 <= minArity && minArity <= maxArity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double applyAsDouble(final double[] args) {
|
||||
return Arrays.stream(args).allMatch(Double::isFinite) ? f.applyAsDouble(args) : Double.NaN;
|
||||
}
|
||||
}
|
||||
|
||||
private static class StringMap<T> {
|
||||
private final List<String> names = new ArrayList<>();
|
||||
private final Map<String, T> values = new HashMap<>();
|
||||
|
||||
public T get(final String name) {
|
||||
return values.get(name);
|
||||
}
|
||||
|
||||
public T random(final ExtendedRandom random) {
|
||||
return get(random.randomItem(names));
|
||||
}
|
||||
|
||||
private boolean isEmpty() {
|
||||
return values.isEmpty();
|
||||
}
|
||||
|
||||
private void put(final String name, final T value) {
|
||||
names.add(name);
|
||||
values.put(name, value);
|
||||
}
|
||||
|
||||
private Collection<T> values() {
|
||||
return values.values();
|
||||
}
|
||||
|
||||
public boolean contains(final String name) {
|
||||
return values.containsKey(name);
|
||||
}
|
||||
}
|
||||
|
||||
public void alias(final String name, final String alias) {
|
||||
aliases.put(name, alias);
|
||||
}
|
||||
|
||||
public String resolve(final String alias) {
|
||||
return aliases.getOrDefault(alias, alias);
|
||||
}
|
||||
}
|
||||
57
common/common/expression/Dialect.java
Normal file
57
common/common/expression/Dialect.java
Normal file
@@ -0,0 +1,57 @@
|
||||
package common.expression;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
/**
|
||||
* Expression dialect.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class Dialect {
|
||||
private final Expr.Cata<String> cata;
|
||||
|
||||
private Dialect(final Expr.Cata<String> cata) {
|
||||
this.cata = cata;
|
||||
}
|
||||
|
||||
public Dialect(final String variable, final String constant, final BiFunction<String, List<String>, String> nary) {
|
||||
this(new Expr.Cata<>(variable::formatted, constant::formatted, name -> name, nary));
|
||||
}
|
||||
|
||||
public Dialect(final String variable, final String constant, final String operation, final String separator) {
|
||||
this(variable, constant, operation(operation, separator));
|
||||
}
|
||||
|
||||
public static BiFunction<String, List<String>, String> operation(final String template, final String separator) {
|
||||
return (op, args) -> template.replace("{op}", op).replace("{args}", String.join(separator, args));
|
||||
}
|
||||
|
||||
public Dialect renamed(final Function<String, String> renamer) {
|
||||
return updated(cata -> cata.withOperation(nary -> (name, args) -> nary.apply(renamer.apply(name), args)));
|
||||
}
|
||||
|
||||
public Dialect updated(final UnaryOperator<Expr.Cata<String>> updater) {
|
||||
return new Dialect(updater.apply(cata));
|
||||
}
|
||||
|
||||
public String render(final Expr expr) {
|
||||
return expr.cata(cata);
|
||||
}
|
||||
|
||||
public String meta(final String name, final String... args) {
|
||||
return cata.operation(name, List.of(args));
|
||||
}
|
||||
|
||||
public Dialect functional() {
|
||||
return renamed(Dialect::toFunctional);
|
||||
}
|
||||
|
||||
private static String toFunctional(final String name) {
|
||||
return name.chars().allMatch(Character::isUpperCase)
|
||||
? name.toLowerCase()
|
||||
: Character.toLowerCase(name.charAt(0)) + name.substring(1);
|
||||
}
|
||||
}
|
||||
157
common/common/expression/Diff.java
Normal file
157
common/common/expression/Diff.java
Normal file
@@ -0,0 +1,157 @@
|
||||
package common.expression;
|
||||
|
||||
import base.Asserts;
|
||||
import base.Named;
|
||||
import common.Engine;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static common.expression.ExprTester.EPS;
|
||||
import static common.expression.ExprTester.Test;
|
||||
|
||||
/**
|
||||
* Expression differentiator.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class Diff {
|
||||
private static final double D = 1e-6;
|
||||
|
||||
private final int base;
|
||||
private final Dialect dialect;
|
||||
|
||||
public Diff(final int base, final Dialect dialect) {
|
||||
this.dialect = dialect;
|
||||
this.base = base;
|
||||
}
|
||||
|
||||
public <X> void diff(final ExprTester<X> tester, final boolean reparse) {
|
||||
tester.addStage(() -> {
|
||||
for (final Test expr : tester.language.getTests()) {
|
||||
checkDiff(tester, expr, reparse, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private <X> List<Engine.Result<String>> checkDiff(
|
||||
final ExprTester<X> tester,
|
||||
final Test test,
|
||||
final boolean reparse,
|
||||
final boolean simplify
|
||||
) {
|
||||
final List<Engine.Result<String>> results = new ArrayList<>(test.variables().size() + 1);
|
||||
System.out.println(" Testing diff: " + test.parsed());
|
||||
|
||||
if (simplify) {
|
||||
final Engine.Result<X> simplified = tester.engine.prepare(dialect.meta("simplify", test.parsed()));
|
||||
test.points().forEachOrdered(point -> {
|
||||
final double[] vars = Arrays.stream(point).map(v -> v + base).toArray();
|
||||
tester.assertValue("simplified expression", simplified, vars, test.evaluate(vars));
|
||||
});
|
||||
results.add(tester.engine.toString(simplified));
|
||||
}
|
||||
|
||||
final double[] indices = IntStream.range(0, test.variables().size()).mapToDouble(a -> a).toArray();
|
||||
for (final Expr variable : test.variables()) {
|
||||
final List<Named<Engine.Result<X>>> ways = new ArrayList<>();
|
||||
final String diffS = dialect.meta("diff", test.parsed(), dialect.render(variable));
|
||||
addWays("diff", tester, reparse, diffS, ways);
|
||||
|
||||
if (simplify) {
|
||||
final String simplifyS = dialect.meta("simplify", diffS);
|
||||
results.add(tester.engine.toString(addWays("simplified", tester, reparse, simplifyS, ways)));
|
||||
}
|
||||
|
||||
final int index = (int) variable.evaluate(indices);
|
||||
|
||||
test.points().forEachOrdered(point -> {
|
||||
final double[] vars = Arrays.stream(point).map(v -> v + base).toArray();
|
||||
final double center = test.evaluate(vars);
|
||||
if (ok(center)) {
|
||||
final double lft = evaluate(test, vars, index, -D);
|
||||
final double rt = evaluate(test, vars, index, D);
|
||||
final double left = (center - lft) / D;
|
||||
final double right = (rt - center) / D;
|
||||
if (ok(lft) && ok(rt) && ok(left) && ok(right) && Math.abs(left - right) < EPS) {
|
||||
for (final Named<Engine.Result<X>> way : ways) {
|
||||
tester.assertValue(
|
||||
"diff by %s, %s".formatted(dialect.render(variable), way.name()),
|
||||
way.value(), vars, (left + right) / 2
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private static <X> Engine.Result<X> addWays(
|
||||
final String name,
|
||||
final ExprTester<X> tester,
|
||||
final boolean reparse,
|
||||
final String exprS,
|
||||
final List<Named<Engine.Result<X>>> ways
|
||||
) {
|
||||
final Engine.Result<X> exprR = tester.engine.prepare(exprS);
|
||||
ways.add(Named.of(name, exprR));
|
||||
if (reparse) {
|
||||
ways.add(Named.of("reparsed " + name, tester.parse(tester.engine.toString(exprR).value())));
|
||||
}
|
||||
return exprR;
|
||||
}
|
||||
|
||||
private static boolean ok(final double value) {
|
||||
final double abs = Math.abs(value);
|
||||
return EPS < abs && abs < 1 / EPS;
|
||||
}
|
||||
|
||||
private static double evaluate(final Test test, final double[] vars, final int index, final double d) {
|
||||
vars[index] += d;
|
||||
final double result = test.evaluate(vars);
|
||||
vars[index] -= d;
|
||||
return result;
|
||||
}
|
||||
|
||||
public <X> void simplify(final ExprTester<X> tester) {
|
||||
final List<int[]> simplifications = tester.language.getSimplifications();
|
||||
if (simplifications == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
tester.addStage(() -> {
|
||||
final List<int[]> newSimplifications = new ArrayList<>();
|
||||
final List<Test> tests = tester.language.getTests();
|
||||
|
||||
for (int i = 0; i < simplifications.size(); i++) {
|
||||
final Test expr = tests.get(i);
|
||||
final int[] expected = simplifications.get(i);
|
||||
final List<Engine.Result<String>> actual = checkDiff(tester, expr, true, true);
|
||||
if (expected != null) {
|
||||
for (int j = 0; j < expected.length; j++) {
|
||||
final Engine.Result<String> result = actual.get(j);
|
||||
final int length = result.value().length();
|
||||
Asserts.assertTrue(
|
||||
"Simplified length too long: %d instead of %d%s"
|
||||
.formatted(length, expected[j], result.context()),
|
||||
length <= expected[j]
|
||||
);
|
||||
}
|
||||
} else {
|
||||
newSimplifications.add(actual.stream().mapToInt(result -> result.value().length()).toArray());
|
||||
}
|
||||
}
|
||||
if (!newSimplifications.isEmpty()) {
|
||||
System.err.println(newSimplifications.stream()
|
||||
.map(row -> Arrays.stream(row)
|
||||
.mapToObj(Integer::toString)
|
||||
.collect(Collectors.joining(", ", "{", "}")))
|
||||
.collect(Collectors.joining(", ", "new int[][]{", "}")));
|
||||
System.err.println(simplifications.size() + " " + newSimplifications.size());
|
||||
throw new AssertionError("Uncovered");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
89
common/common/expression/Expr.java
Normal file
89
common/common/expression/Expr.java
Normal file
@@ -0,0 +1,89 @@
|
||||
package common.expression;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
/**
|
||||
* Expression instance.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class Expr {
|
||||
private final ExprTester.Func answer;
|
||||
/* There are no forall generics in Java, so using Object as placeholder. */
|
||||
private final Function<Cata<Object>, Object> coCata;
|
||||
|
||||
private Expr(final ExprTester.Func answer, final Function<Cata<Object>, Object> coCata) {
|
||||
this.answer = answer;
|
||||
this.coCata = coCata;
|
||||
}
|
||||
|
||||
public <T> T cata(final Cata<T> cata) {
|
||||
@SuppressWarnings("unchecked")
|
||||
final Function<Cata<T>, T> coCata = (Function<Cata<T>, T>) (Function<?, ?>) this.coCata;
|
||||
return coCata.apply(cata);
|
||||
}
|
||||
|
||||
public double evaluate(final double... vars) {
|
||||
return answer.applyAsDouble(vars);
|
||||
}
|
||||
|
||||
static Expr f(final String name, final ExprTester.Func operator, final List<Expr> args) {
|
||||
Objects.requireNonNull(operator, "Unknown operation " + name);
|
||||
return new Expr(
|
||||
vars -> operator.applyAsDouble(args.stream().mapToDouble(arg -> arg.evaluate(vars)).toArray()),
|
||||
cata -> cata.operation(
|
||||
name,
|
||||
args.stream().map(arg -> arg.cata(cata)).toList()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
static Expr constant(final int value) {
|
||||
return new Expr(vars -> value, cata -> cata.constant(value));
|
||||
}
|
||||
|
||||
static Expr variable(final String name, final int index) {
|
||||
return new Expr(vars -> vars[index], cata -> cata.variable(name));
|
||||
}
|
||||
|
||||
static Expr nullary(final String name, final ExprTester.Func answer) {
|
||||
return new Expr(answer, cata -> cata.nullary(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Expression catamorphism.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public record Cata<T>(
|
||||
Function<String, T> variable,
|
||||
IntFunction<T> constant,
|
||||
Function<String, T> nullary,
|
||||
BiFunction<String, List<T>, T> operation
|
||||
) {
|
||||
public T variable(final String name) {
|
||||
return variable.apply(name);
|
||||
}
|
||||
|
||||
public T constant(final int value) {
|
||||
return constant.apply(value);
|
||||
}
|
||||
|
||||
public T nullary(final String name) {
|
||||
return nullary.apply(name);
|
||||
}
|
||||
|
||||
public T operation(final String name, final List<T> args) {
|
||||
return operation.apply(name, args);
|
||||
}
|
||||
|
||||
public Cata<T> withOperation(final UnaryOperator<BiFunction<String, List<T>, T>> updater) {
|
||||
return new Cata<>(variable, constant, nullary, updater.apply(operation));
|
||||
}
|
||||
}
|
||||
}
|
||||
221
common/common/expression/ExprTester.java
Normal file
221
common/common/expression/ExprTester.java
Normal file
@@ -0,0 +1,221 @@
|
||||
package common.expression;
|
||||
|
||||
import base.Asserts;
|
||||
import base.ExtendedRandom;
|
||||
import base.TestCounter;
|
||||
import base.Tester;
|
||||
import common.Engine;
|
||||
import common.EngineException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.ToDoubleFunction;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Expressions tester.
|
||||
*
|
||||
* @author Niyaz Nigmatullin
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class ExprTester<E> extends Tester {
|
||||
public static final int N = 128;
|
||||
public static final double EPS = 1e-3;
|
||||
public static final int RANDOM_TESTS = 444;
|
||||
|
||||
private final int randomTests;
|
||||
/*package*/ final Engine<E> engine;
|
||||
/*package*/ final Language language;
|
||||
private final List<Runnable> stages = new ArrayList<>();
|
||||
private final boolean testToString;
|
||||
|
||||
private final Generator<String> spoiler;
|
||||
private final Generator<BadInput> corruptor;
|
||||
|
||||
public static final Generator<String> STANDARD_SPOILER = (input, expr, random, builder) -> builder
|
||||
.add(input)
|
||||
.add(addSpaces(input, random));
|
||||
|
||||
public ExprTester(
|
||||
final TestCounter counter,
|
||||
final int randomTests,
|
||||
final Engine<E> engine,
|
||||
final Language language,
|
||||
final boolean testToString,
|
||||
final Generator<String> spoiler,
|
||||
final Generator<BadInput> corruptor
|
||||
) {
|
||||
super(counter);
|
||||
this.randomTests = randomTests;
|
||||
this.engine = engine;
|
||||
this.language = language;
|
||||
this.testToString = testToString;
|
||||
this.spoiler = spoiler;
|
||||
this.corruptor = corruptor;
|
||||
}
|
||||
|
||||
private static final Predicate<String> UNSAFE = Pattern.compile("[-\\p{Alnum}+*/.=&|^<>◀▶◁▷≤≥?⁰-⁹₀-₉:]").asPredicate();
|
||||
|
||||
private static boolean safe(final char ch) {
|
||||
return !UNSAFE.test("" + ch);
|
||||
}
|
||||
|
||||
public static String addSpaces(final String expression, final ExtendedRandom random) {
|
||||
String spaced = expression;
|
||||
for (int n = StrictMath.min(10, 200 / expression.length()); n > 0;) {
|
||||
final int index = random.nextInt(spaced.length() + 1);
|
||||
final char c = index == 0 ? 0 : spaced.charAt(index - 1);
|
||||
final char nc = index == spaced.length() ? 0 : spaced.charAt(index);
|
||||
if ((safe(c) || safe(nc)) && c != '\'' && nc != '\'' && c != '"' && nc != '"') {
|
||||
spaced = spaced.substring(0, index) + " " + spaced.substring(index);
|
||||
n--;
|
||||
}
|
||||
}
|
||||
return spaced;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void test() {
|
||||
for (final Test test : language.getTests()) {
|
||||
try {
|
||||
test(test, prepared -> counter.scope(
|
||||
"Testing: " + prepared,
|
||||
() -> test.points().forEachOrdered(vars -> assertValue(
|
||||
"original expression",
|
||||
prepared,
|
||||
vars,
|
||||
test.evaluate(vars)
|
||||
)))
|
||||
);
|
||||
} catch (final RuntimeException | AssertionError e) {
|
||||
throw new AssertionError("Error while testing " + test.parsed() + ": " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
counter.scope("Random tests", () -> testRandom(randomTests));
|
||||
stages.forEach(Runnable::run);
|
||||
}
|
||||
|
||||
public static int limit(final int variables) {
|
||||
return (int) Math.floor(Math.pow(N, 1.0 / variables));
|
||||
}
|
||||
|
||||
private void test(final Test test, final Consumer<Engine.Result<E>> check) {
|
||||
final Consumer<Engine.Result<E>> fullCheck = parsed -> counter.test(() -> {
|
||||
check.accept(parsed);
|
||||
if (testToString) {
|
||||
counter.test(() -> engine.toString(parsed).assertEquals(test.toStr()));
|
||||
}
|
||||
});
|
||||
fullCheck.accept(engine.prepare(test.parsed()));
|
||||
spoiler.forEach(10, test, random(), input -> fullCheck.accept(parse(input)));
|
||||
corruptor.forEach(3, test, random(), input -> input.assertError(this::parse));
|
||||
}
|
||||
|
||||
public Engine.Result<E> parse(final String expression) {
|
||||
return engine.parse(expression);
|
||||
}
|
||||
|
||||
public void testRandom(final int n) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (i % 100 == 0) {
|
||||
counter.format("Completed %3d out of %d%n", i, n);
|
||||
}
|
||||
final double[] vars = language.randomVars();
|
||||
|
||||
final Test test = language.randomTest(i);
|
||||
final double answer = test.evaluate(vars);
|
||||
|
||||
test(test, prepared -> assertValue("random expression", prepared, vars, answer));
|
||||
}
|
||||
}
|
||||
|
||||
public void assertValue(final String context, final Engine.Result<E> prepared, final double[] vars, final double expected) {
|
||||
counter.test(() -> {
|
||||
final Engine.Result<Number> result = engine.evaluate(prepared, vars);
|
||||
Asserts.assertEquals("%n\tFor %s%s".formatted(context, result.context()), expected, result.value().doubleValue(), EPS);
|
||||
});
|
||||
}
|
||||
|
||||
public static int mode(final String[] args, final Class<?> type, final String... modes) {
|
||||
if (args.length == 0) {
|
||||
System.err.println("ERROR: No arguments found");
|
||||
} else if (args.length > 1) {
|
||||
System.err.println("ERROR: Only one argument expected, " + args.length + " found");
|
||||
} else if (!Arrays.asList(modes).contains(args[0])) {
|
||||
System.err.println("ERROR: First argument should be one of: \"" + String.join("\", \"", modes) + "\", found: \"" + args[0] + "\"");
|
||||
} else {
|
||||
return Arrays.asList(modes).indexOf(args[0]);
|
||||
}
|
||||
System.err.println("Usage: java -ea " + type.getName() + " {" + String.join("|", modes) + "}");
|
||||
System.exit(1);
|
||||
throw new AssertionError("Return from System.exit");
|
||||
}
|
||||
|
||||
public void addStage(final Runnable stage) {
|
||||
stages.add(stage);
|
||||
}
|
||||
|
||||
public interface Func extends ToDoubleFunction<double[]> {
|
||||
@Override
|
||||
double applyAsDouble(double... args);
|
||||
}
|
||||
|
||||
public record Test(Expr expr, String parsed, String unparsed, String toStr, List<Expr> variables) {
|
||||
public double evaluate(final double... vars) {
|
||||
return expr.evaluate(vars);
|
||||
}
|
||||
|
||||
public Stream<double[]> points() {
|
||||
final int n = limit(variables.size());
|
||||
return IntStream.range(0, N).mapToObj(i -> IntStream.iterate(i, j -> j / n)
|
||||
.map(j -> j % n)
|
||||
.limit(variables.size())
|
||||
.mapToDouble(j -> j)
|
||||
.toArray());
|
||||
}
|
||||
}
|
||||
|
||||
public record BadInput(String prefix, String comment, String suffix) {
|
||||
public String assertError(final Function<String, Engine.Result<?>> parse) {
|
||||
try {
|
||||
final Engine.Result<?> parsed = parse.apply(prefix + suffix);
|
||||
throw new AssertionError("Parsing error expected for '%s%s%s', got %s"
|
||||
.formatted(prefix, comment, suffix, parsed.value()));
|
||||
} catch (final EngineException e) {
|
||||
return e.getCause().getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface Generator<T> {
|
||||
void generate(String input, Expr expr, ExtendedRandom random, Stream.Builder<? super T> builder);
|
||||
|
||||
static <T> Generator<T> empty() {
|
||||
return (i, e, r, b) -> {};
|
||||
}
|
||||
|
||||
default Generator<T> combine(final Generator<? extends T> that) {
|
||||
return (i, e, r, b) -> {
|
||||
this.generate(i, e, r, b);
|
||||
that.generate(i, e, r, b);
|
||||
};
|
||||
}
|
||||
|
||||
default void forEach(final int limit, final Test test, final ExtendedRandom random, final Consumer<? super T> consumer) {
|
||||
final Stream.Builder<T> builder = Stream.builder();
|
||||
generate(test.unparsed(), test.expr(), random, builder);
|
||||
builder.build()
|
||||
.sorted(Comparator.comparingInt(Object::hashCode))
|
||||
.limit(limit)
|
||||
.forEach(consumer);
|
||||
}
|
||||
}
|
||||
}
|
||||
64
common/common/expression/Language.java
Normal file
64
common/common/expression/Language.java
Normal file
@@ -0,0 +1,64 @@
|
||||
package common.expression;
|
||||
|
||||
import common.expression.ExprTester.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Expression language.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class Language {
|
||||
private final Dialect parsed;
|
||||
private final Dialect unparsed;
|
||||
private final Dialect toString;
|
||||
private final BaseVariant variant;
|
||||
private final List<Test> tests;
|
||||
private final List<int[]> simplifications;
|
||||
|
||||
public Language(
|
||||
final Dialect parsed,
|
||||
final Dialect unparsed,
|
||||
final Dialect toString,
|
||||
final BaseVariant variant,
|
||||
final List<int[]> simplifications
|
||||
) {
|
||||
this.parsed = parsed;
|
||||
this.unparsed = unparsed;
|
||||
this.toString = toString;
|
||||
this.variant = variant;
|
||||
|
||||
tests = variant.getTests().stream().map(this::test).toList();
|
||||
assert simplifications == null || simplifications.isEmpty() || simplifications.size() == tests.size();
|
||||
this.simplifications = simplifications != null && simplifications.isEmpty()
|
||||
? Collections.nCopies(tests.size(), null) : simplifications;
|
||||
}
|
||||
|
||||
private Test test(final Expr expr) {
|
||||
return new Test(
|
||||
expr,
|
||||
parsed.render(expr),
|
||||
unparsed.render(expr),
|
||||
toString.render(expr),
|
||||
variant.getVariables()
|
||||
);
|
||||
}
|
||||
|
||||
public Test randomTest(final int size) {
|
||||
return test(variant.randomTest(size));
|
||||
}
|
||||
|
||||
public double[] randomVars() {
|
||||
return variant.random().getRandom().doubles().limit(variant.getVariables().size()).toArray();
|
||||
}
|
||||
|
||||
public List<Test> getTests() {
|
||||
return tests;
|
||||
}
|
||||
|
||||
public List<int[]> getSimplifications() {
|
||||
return simplifications;
|
||||
}
|
||||
}
|
||||
79
common/common/expression/LanguageBuilder.java
Normal file
79
common/common/expression/LanguageBuilder.java
Normal file
@@ -0,0 +1,79 @@
|
||||
package common.expression;
|
||||
|
||||
import base.Selector;
|
||||
import base.TestCounter;
|
||||
import base.Tester;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntPredicate;
|
||||
|
||||
/**
|
||||
* Expression test builder.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class LanguageBuilder {
|
||||
public final OperationsBuilder ops;
|
||||
private List<int[]> simplifications;
|
||||
private Function<Expr.Cata<String>, Expr.Cata<String>> toStr = Function.identity();
|
||||
private ExprTester.Generator<ExprTester.BadInput> corruptor = ExprTester.Generator.empty();
|
||||
|
||||
public LanguageBuilder(final boolean testMulti, final List<String> variables) {
|
||||
ops = new ArithmeticBuilder(testMulti, variables);
|
||||
}
|
||||
|
||||
public static Selector.Composite<LanguageBuilder> selector(
|
||||
final Class<?> owner,
|
||||
final IntPredicate testMulti,
|
||||
final List<String> variables,
|
||||
final BiFunction<LanguageBuilder, TestCounter, Tester> tester,
|
||||
final String... modes
|
||||
) {
|
||||
return Selector.composite(
|
||||
owner,
|
||||
counter -> new LanguageBuilder(testMulti.test(counter.mode()), variables),
|
||||
(builder, counter) -> tester.apply(builder, counter).test(),
|
||||
modes
|
||||
);
|
||||
}
|
||||
|
||||
public static Selector.Composite<LanguageBuilder> selector(
|
||||
final Class<?> owner,
|
||||
final IntPredicate testMulti,
|
||||
final BiFunction<LanguageBuilder, TestCounter, Tester> tester,
|
||||
final String... modes
|
||||
) {
|
||||
return selector(owner, testMulti, List.of("x", "y", "z"), tester, modes);
|
||||
}
|
||||
|
||||
public Variant variant() {
|
||||
return ops.variant();
|
||||
}
|
||||
|
||||
public Language language(final Dialect parsed, final Dialect unparsed) {
|
||||
final BaseVariant variant = ops.variant();
|
||||
return new Language(parsed.renamed(variant::resolve), unparsed.updated(toStr::apply), unparsed, variant, simplifications);
|
||||
}
|
||||
|
||||
public void toStr(final Function<Expr.Cata<String>, Expr.Cata<String>> updater) {
|
||||
toStr = updater.compose(toStr);
|
||||
}
|
||||
|
||||
public Function<Expr.Cata<String>, Expr.Cata<String>> getToStr() {
|
||||
return toStr;
|
||||
}
|
||||
|
||||
public void setSimplifications(final List<int[]> simplifications) {
|
||||
this.simplifications = simplifications;
|
||||
}
|
||||
|
||||
public void addCorruptor(final ExprTester.Generator<ExprTester.BadInput> corruptor) {
|
||||
this.corruptor = this.corruptor.combine(corruptor);
|
||||
}
|
||||
|
||||
public ExprTester.Generator<ExprTester.BadInput> getCorruptor() {
|
||||
return corruptor;
|
||||
}
|
||||
}
|
||||
11
common/common/expression/Operation.java
Normal file
11
common/common/expression/Operation.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package common.expression;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Expression operation.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public interface Operation extends Consumer<LanguageBuilder> {
|
||||
}
|
||||
195
common/common/expression/Operations.java
Normal file
195
common/common/expression/Operations.java
Normal file
@@ -0,0 +1,195 @@
|
||||
package common.expression;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.OptionalDouble;
|
||||
import java.util.function.DoubleBinaryOperator;
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.ToDoubleFunction;
|
||||
import java.util.stream.DoubleStream;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Known expression operations.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
|
||||
public enum Operations {
|
||||
;
|
||||
|
||||
public static final Operation ARITH = builder -> {
|
||||
builder.ops.alias("negate", "Negate");
|
||||
builder.ops.alias("+", "Add");
|
||||
builder.ops.alias("-", "Subtract");
|
||||
builder.ops.alias("*", "Multiply");
|
||||
builder.ops.alias("/", "Divide");
|
||||
};
|
||||
|
||||
public static final Operation NARY_ARITH = builder -> {
|
||||
builder.ops.remove("negate", "+", "-", "*", "/");
|
||||
|
||||
builder.ops.unary("negate", "Negate", a -> -a);
|
||||
|
||||
builder.ops.any("+", "Add", arith(0, Double::sum, 0));
|
||||
builder.ops.any("-", "Subtract", arith(0, (a, b) -> a - b, 1));
|
||||
builder.ops.any("*", "Multiply", arith(1, (a, b) -> a * b, 0));
|
||||
builder.ops.any("/", "Divide", arith(1, (a, b) -> a / b, 1));
|
||||
};
|
||||
|
||||
|
||||
// === Common
|
||||
|
||||
public static Operation constant(final String name, final double value) {
|
||||
return constant(name, name, value);
|
||||
}
|
||||
|
||||
public static Operation constant(final String name, final String alias, final double value) {
|
||||
return builder -> builder.ops.constant(name, alias, value);
|
||||
}
|
||||
|
||||
public static Operation unary(final String name, final String alias, final DoubleUnaryOperator op) {
|
||||
return builder -> builder.ops.unary(name, alias, op);
|
||||
}
|
||||
|
||||
public static Operation binary(final String name, final String alias, final DoubleBinaryOperator op) {
|
||||
return builder -> builder.ops.binary(name, alias, op);
|
||||
}
|
||||
|
||||
public static AnyOp arith(final double zero, final DoubleBinaryOperator f, final int minArity) {
|
||||
final ExprTester.Func func = args -> args.length == 0 ? zero
|
||||
: args.length == 1 ? f.applyAsDouble(zero, args[0])
|
||||
: Arrays.stream(args).reduce(f).orElseThrow();
|
||||
return new AnyOp(func, minArity, minArity + 5, 2);
|
||||
}
|
||||
|
||||
|
||||
// === More common
|
||||
|
||||
public record Op(String name, String alias, int minArity, int maxArity, ExprTester.Func f) {
|
||||
public Operation fix(final int arity) {
|
||||
assert minArity <= arity && arity <= maxArity;
|
||||
final String patched =
|
||||
name.contains("₀") ? name.replace("₀", fix(arity, '₀')) :
|
||||
name.contains("0") ? name.replace("0", fix(arity, '0')) :
|
||||
name + fix(arity, name.charAt(0) > 0xff ? '₀' : '0');
|
||||
return fixed(patched, alias + arity, arity, f);
|
||||
}
|
||||
|
||||
private static String fix(final int arity, final char c) {
|
||||
return "" + (char) (c + arity);
|
||||
}
|
||||
|
||||
public Operation any(final int fixedArity) {
|
||||
return checker -> checker.ops.any(name, alias, new AnyOp(f, minArity, maxArity, fixedArity));
|
||||
}
|
||||
}
|
||||
|
||||
public static Op op(final String name, final String alias, final int minArity, final ExprTester.Func f) {
|
||||
return new Op(name, alias, minArity, minArity + 5, f);
|
||||
}
|
||||
|
||||
public static Op op1(final String alias, final int minArity, final ExprTester.Func f) {
|
||||
return new Op(Character.toLowerCase(alias.charAt(0)) + alias.substring(1), alias, minArity, minArity + 5, f);
|
||||
}
|
||||
|
||||
public static Op opS(final String name, final String alias, final int minArity, final ToDoubleFunction<DoubleStream> f) {
|
||||
return op(name, alias, minArity, args -> f.applyAsDouble(Arrays.stream(args)));
|
||||
}
|
||||
|
||||
public static Op opO(final String name, final String alias, final int minArity, final Function<DoubleStream, OptionalDouble> f) {
|
||||
return opS(name, alias, minArity, f.andThen(OptionalDouble::orElseThrow)::apply);
|
||||
}
|
||||
|
||||
public static Operation fixed(final String name, final String alias, final int arity, final ExprTester.Func f) {
|
||||
return builder -> builder.ops.fixed(name, alias, arity, f);
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
public static Operation range(final int min, final int max, final Op... ops) {
|
||||
final List<Operation> operations = IntStream.rangeClosed(min, max)
|
||||
.mapToObj(i -> Arrays.stream(ops).map(op -> op.fix(i)))
|
||||
.flatMap(Function.identity())
|
||||
.toList();
|
||||
return builder -> operations.forEach(op -> op.accept(builder));
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
public static Operation any(final int fixed, final Op... ops) {
|
||||
final List<Operation> operations = Arrays.stream(ops).map(op -> op.any(fixed)).toList();
|
||||
return builder -> operations.forEach(op -> op.accept(builder));
|
||||
}
|
||||
|
||||
public static Operation infix(
|
||||
final String name,
|
||||
final String alias,
|
||||
final int priority,
|
||||
final DoubleBinaryOperator op
|
||||
) {
|
||||
return checker -> checker.ops.infix(name, alias, priority, op);
|
||||
}
|
||||
|
||||
|
||||
// === Variables
|
||||
public static final Operation VARIABLES = builder ->
|
||||
Stream.of("y", "z").forEach(builder.ops::variable);
|
||||
|
||||
|
||||
// === OneTwo
|
||||
public static final Operation ONE = constant("one", 1);
|
||||
public static final Operation TWO = constant("two", 2);
|
||||
public static final Operation THREE = constant("three", 3);
|
||||
|
||||
|
||||
// === Clamp, wrap
|
||||
public static final Operation CLAMP = fixed("clamp", "Clamp", 3, args ->
|
||||
args[1] <= args[2] ? Math.min(Math.max(args[0], args[1]), args[2]) : Double.NaN);
|
||||
public static final Operation WRAP = fixed("wrap", "Wrap", 3, args ->
|
||||
args[1] < args[2]
|
||||
? args[0] - Math.floor((args[0] - args[1]) / (args[2] - args[1])) * (args[2] - args[1])
|
||||
: Double.NaN);
|
||||
|
||||
// === ArcTan
|
||||
public static final Operation ATAN = unary("atan", "ArcTan", Math::atan);
|
||||
public static final Operation ATAN2 = binary("atan2", "ArcTan2", Math::atan2);
|
||||
|
||||
// === ArgMin, ArgMax
|
||||
|
||||
private static Op arg(final String name, final String alias, final Function<DoubleStream, OptionalDouble> f) {
|
||||
return opO(
|
||||
name, "Arg" + alias, 1, args -> {
|
||||
final double[] values = args.toArray();
|
||||
return f.apply(Arrays.stream(values)).stream()
|
||||
.flatMap(value -> IntStream.range(0, values.length)
|
||||
.filter(i -> values[i] == value).asDoubleStream())
|
||||
.findFirst();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static final Op ARG_MIN = arg("argMin", "Min", DoubleStream::min);
|
||||
public static final Op ARG_MAX = arg("argMax", "Max", DoubleStream::max);
|
||||
|
||||
|
||||
// === SoftClamp
|
||||
public static final Operation SOFT_CLAMP = fixed("softClamp", "SoftClamp", 4, args ->
|
||||
args[1] <= args[2] && args[3] > 0
|
||||
? args[1] + (args[2] - args[1]) / (1 + Math.exp(args[3] * ((args[2] + args[1]) / 2 - args[0])))
|
||||
: Double.NaN);
|
||||
|
||||
|
||||
// === SinCos
|
||||
public static final Operation SIN = unary("sin", "Sin", Math::sin);
|
||||
public static final Operation COS = unary("cos", "Cos", Math::cos);
|
||||
|
||||
|
||||
// === Pow, Log
|
||||
public static final Operation POW = binary("pow", "Power", Math::pow);
|
||||
public static final Operation LOG = binary("log", "Log", (a, b) -> Math.log(Math.abs(b)) / Math.log(Math.abs(a)));
|
||||
|
||||
|
||||
// === Sum
|
||||
public static final Op SUM = opS("sum", "Sum", 0, DoubleStream::sum);
|
||||
}
|
||||
31
common/common/expression/OperationsBuilder.java
Normal file
31
common/common/expression/OperationsBuilder.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package common.expression;
|
||||
|
||||
import java.util.function.DoubleBinaryOperator;
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
|
||||
/**
|
||||
* Operations builder.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public interface OperationsBuilder {
|
||||
void constant(String name, String alias, double value);
|
||||
|
||||
void variable(String name);
|
||||
|
||||
void unary(String name, String alias, DoubleUnaryOperator op);
|
||||
|
||||
void binary(String name, String alias, DoubleBinaryOperator op);
|
||||
|
||||
void infix(String name, String alias, int priority, DoubleBinaryOperator op);
|
||||
|
||||
void fixed(String name, String alias, int arity, ExprTester.Func f);
|
||||
|
||||
void any(String name, String alias, AnyOp op);
|
||||
|
||||
void alias(String name, String alias);
|
||||
|
||||
void remove(String... names);
|
||||
|
||||
BaseVariant variant();
|
||||
}
|
||||
16
common/common/expression/Variant.java
Normal file
16
common/common/expression/Variant.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package common.expression;
|
||||
|
||||
import base.ExtendedRandom;
|
||||
|
||||
/**
|
||||
* Expression variant meta-info.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public interface Variant {
|
||||
ExtendedRandom random();
|
||||
|
||||
boolean hasVarargs();
|
||||
|
||||
Integer getPriority(String op);
|
||||
}
|
||||
39
java/expression/Abs.java
Normal file
39
java/expression/Abs.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package expression;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class Abs extends AbstractExpression {
|
||||
private final AbstractExpression operand;
|
||||
|
||||
public Abs(AbstractExpression operand) {
|
||||
this.operand = operand;
|
||||
}
|
||||
|
||||
private static int absInt(int n) {
|
||||
if (n == Integer.MIN_VALUE) throw new OverflowException("abs");
|
||||
return n < 0 ? -n : n;
|
||||
}
|
||||
|
||||
@Override public int evaluate(int x) { return absInt(operand.evaluate(x)); }
|
||||
@Override public int evaluate(int x, int y, int z) { return absInt(operand.evaluate(x,y,z)); }
|
||||
@Override public int evaluate(List<Integer> vars) { return absInt(operand.evaluate(vars)); }
|
||||
@Override public BigInteger evaluateBi(List<BigInteger> vars) { return operand.evaluateBi(vars).abs(); }
|
||||
@Override public BigDecimal evaluateBd(List<BigDecimal> vars) { return operand.evaluateBd(vars).abs(); }
|
||||
|
||||
@Override public String toString() { return "‖" + operand + "‖"; }
|
||||
@Override public String toMiniString() { return "‖" + operand.toMiniString() + "‖"; }
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Abs)) return false;
|
||||
return operand.equals(((Abs) obj).operand);
|
||||
}
|
||||
|
||||
@Override public int hashCode() { return operand.hashCode() ^ 0x41425321; }
|
||||
}
|
||||
114
java/expression/AbstractBinaryOperation.java
Normal file
114
java/expression/AbstractBinaryOperation.java
Normal file
@@ -0,0 +1,114 @@
|
||||
package expression;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public abstract class AbstractBinaryOperation extends AbstractExpression {
|
||||
|
||||
protected final AbstractExpression left;
|
||||
protected final AbstractExpression right;
|
||||
|
||||
protected AbstractBinaryOperation(
|
||||
AbstractExpression left,
|
||||
AbstractExpression right
|
||||
) {
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
protected abstract String getOperator();
|
||||
|
||||
protected abstract int getPriority();
|
||||
|
||||
protected abstract boolean isRightAssoc();
|
||||
|
||||
protected abstract int applyInt(int a, int b);
|
||||
|
||||
protected abstract BigInteger applyBi(BigInteger a, BigInteger b);
|
||||
|
||||
protected abstract BigDecimal applyBd(BigDecimal a, BigDecimal b);
|
||||
|
||||
@Override
|
||||
public int evaluate(int x) {
|
||||
return applyInt(left.evaluate(x), right.evaluate(x));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int evaluate(int x, int y, int z) {
|
||||
return applyInt(left.evaluate(x, y, z), right.evaluate(x, y, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int evaluate(List<Integer> vars) {
|
||||
return applyInt(left.evaluate(vars), right.evaluate(vars));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger evaluateBi(List<BigInteger> vars) {
|
||||
return applyBi(left.evaluateBi(vars), right.evaluateBi(vars));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal evaluateBd(List<BigDecimal> vars) {
|
||||
return applyBd(left.evaluateBd(vars), right.evaluateBd(vars));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "(" + left + " " + getOperator() + " " + right + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toMiniString() {
|
||||
return miniLeft() + " " + getOperator() + " " + miniRight();
|
||||
}
|
||||
|
||||
private String miniLeft() {
|
||||
if (
|
||||
left instanceof AbstractBinaryOperation op &&
|
||||
op.getPriority() < getPriority()
|
||||
) {
|
||||
return "(" + op.toMiniString() + ")";
|
||||
}
|
||||
return left.toMiniString();
|
||||
}
|
||||
|
||||
private String miniRight() {
|
||||
if (right instanceof AbstractBinaryOperation op) {
|
||||
boolean samePriority = op.getPriority() == getPriority();
|
||||
boolean lowerPriority = op.getPriority() < getPriority();
|
||||
boolean needParens =
|
||||
lowerPriority ||
|
||||
(samePriority && isRightAssoc()) ||
|
||||
(samePriority && op.isRightAssoc() && getPriority() > 1) ||
|
||||
(samePriority &&
|
||||
!getOperator().equals(op.getOperator()) &&
|
||||
!op.isRightAssoc() &&
|
||||
!isRightAssoc());
|
||||
if (needParens) {
|
||||
return "(" + op.toMiniString() + ")";
|
||||
}
|
||||
}
|
||||
return right.toMiniString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null || getClass() != obj.getClass()) return false;
|
||||
AbstractBinaryOperation other = (AbstractBinaryOperation) obj;
|
||||
return left.equals(other.left) && right.equals(other.right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (
|
||||
31 * (31 * left.hashCode() + right.hashCode()) +
|
||||
getClass().hashCode()
|
||||
);
|
||||
}
|
||||
}
|
||||
42
java/expression/AbstractExpression.java
Normal file
42
java/expression/AbstractExpression.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package expression;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public abstract class AbstractExpression
|
||||
implements
|
||||
Expression,
|
||||
TripleExpression,
|
||||
ListExpression,
|
||||
BigIntegerListExpression,
|
||||
BigDecimalListExpression
|
||||
{
|
||||
|
||||
@Override
|
||||
public abstract int evaluate(int x);
|
||||
|
||||
@Override
|
||||
public abstract int evaluate(int x, int y, int z);
|
||||
|
||||
@Override
|
||||
public abstract int evaluate(List<Integer> vars);
|
||||
|
||||
@Override
|
||||
public abstract BigInteger evaluateBi(List<BigInteger> vars);
|
||||
|
||||
@Override
|
||||
public abstract BigDecimal evaluateBd(List<BigDecimal> vars);
|
||||
|
||||
@Override
|
||||
public abstract String toString();
|
||||
|
||||
@Override
|
||||
public abstract boolean equals(Object obj);
|
||||
|
||||
@Override
|
||||
public abstract int hashCode();
|
||||
}
|
||||
44
java/expression/Add.java
Normal file
44
java/expression/Add.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package expression;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class Add extends AbstractBinaryOperation {
|
||||
|
||||
public Add(AbstractExpression l, AbstractExpression r) {
|
||||
super(l, r);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getOperator() {
|
||||
return "+";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPriority() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isRightAssoc() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int applyInt(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BigInteger applyBi(BigInteger a, BigInteger b) {
|
||||
return a.add(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BigDecimal applyBd(BigDecimal a, BigDecimal b) {
|
||||
return a.add(b);
|
||||
}
|
||||
}
|
||||
363
java/expression/BigDecimalListExpression.java
Normal file
363
java/expression/BigDecimalListExpression.java
Normal file
@@ -0,0 +1,363 @@
|
||||
package expression;
|
||||
|
||||
import base.Asserts;
|
||||
import base.Pair;
|
||||
import base.TestCounter;
|
||||
import expression.common.ExpressionKind;
|
||||
import expression.common.Type;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* One-argument arithmetic expression over {@link BigDecimal}s.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@SuppressWarnings("ClassReferencesSubclass")
|
||||
public interface BigDecimalListExpression extends ToMiniString {
|
||||
BigDecimal evaluateBd(List<BigDecimal> variables);
|
||||
|
||||
// Tests follow. You may temporarily remove everything til the end.
|
||||
|
||||
Add EXAMPLE = new Add(
|
||||
new Subtract(new Variable(0), new Const(BigDecimal.ONE)),
|
||||
new Multiply(new Variable(1), new Const(BigDecimal.TEN))
|
||||
);
|
||||
|
||||
Type<BigDecimal> TYPE = new Type<>(
|
||||
v -> new BigDecimal(v + ".000"),
|
||||
random -> BigDecimal.valueOf(random.getRandom().nextGaussian()),
|
||||
BigDecimal.class
|
||||
);
|
||||
ExpressionKind<BigDecimalListExpression, BigDecimal> KIND =
|
||||
new ExpressionKind<>(
|
||||
TYPE,
|
||||
BigDecimalListExpression.class,
|
||||
(r, c) ->
|
||||
IntStream.range(0, c)
|
||||
.mapToObj(name ->
|
||||
Pair.<String, BigDecimalListExpression>of(
|
||||
"$" + name,
|
||||
new Variable(name)
|
||||
)
|
||||
)
|
||||
.toList(),
|
||||
(expr, variables, values) -> expr.evaluateBd(values)
|
||||
);
|
||||
|
||||
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
|
||||
static ExpressionTester<?, ?> tester(final TestCounter counter) {
|
||||
Asserts.assertEquals(
|
||||
"Example toString()",
|
||||
"(($0 - 1) + ($1 * 10))",
|
||||
EXAMPLE.toString()
|
||||
);
|
||||
Asserts.assertEquals(
|
||||
EXAMPLE + " at (2, 3)",
|
||||
BigDecimal.valueOf(31),
|
||||
EXAMPLE.evaluateBd(
|
||||
List.of(BigDecimal.valueOf(2), BigDecimal.valueOf(3))
|
||||
)
|
||||
);
|
||||
|
||||
final Variable vx = new Variable(0);
|
||||
final Variable vy = new Variable(1);
|
||||
|
||||
return new ExpressionTester<>(
|
||||
counter,
|
||||
KIND,
|
||||
c -> v -> c,
|
||||
(op, a, b) -> v -> op.apply(a.evaluateBd(v), b.evaluateBd(v)),
|
||||
BigDecimal::add,
|
||||
BigDecimal::subtract,
|
||||
BigDecimal::multiply,
|
||||
BigDecimal::divide
|
||||
)
|
||||
.basic("10", "10", v -> v(10), c(10))
|
||||
.basic("$x", "$x", BigDecimalListExpression::x, vx)
|
||||
.basic("$y", "$y", BigDecimalListExpression::y, vy)
|
||||
.basic("($x + $y)", "$x + $y", v -> x(v).add(y(v)), new Add(vx, vy))
|
||||
.basic("($x + 2)", "$x + 2", v -> x(v).add(v(2)), new Add(vx, c(2)))
|
||||
.basic(
|
||||
"(2 - $x)",
|
||||
"2 - $x",
|
||||
v -> v(2).subtract(x(v)),
|
||||
new Subtract(c(2), vx)
|
||||
)
|
||||
.basic(
|
||||
"(3 * $x)",
|
||||
"3 * $x",
|
||||
v -> v(3).multiply(x(v)),
|
||||
new Multiply(c(3), vx)
|
||||
)
|
||||
.basic("($x + $x)", "$x + $x", v -> x(v).add(x(v)), new Add(vx, vx))
|
||||
.basic(
|
||||
"($x / -2)",
|
||||
"$x / -2",
|
||||
v -> x(v).divide(v(-2)),
|
||||
new Divide(vx, c(-2))
|
||||
)
|
||||
.basic("(2 + $x)", "2 + $x", v -> v(2).add(x(v)), new Add(c(2), vx))
|
||||
.basic(
|
||||
"((1 + 2) + 3)",
|
||||
"1 + 2 + 3",
|
||||
v -> v(6),
|
||||
new Add(new Add(c(1), c(2)), c(3))
|
||||
)
|
||||
.basic(
|
||||
"(1 + (2 * 3))",
|
||||
"1 + 2 * 3",
|
||||
v -> v(7),
|
||||
new Add(c(1), new Multiply(c(2), c(3)))
|
||||
)
|
||||
.basic(
|
||||
"(1 - (2 * 3))",
|
||||
"1 - 2 * 3",
|
||||
v -> v(-5),
|
||||
new Subtract(c(1), new Multiply(c(2), c(3)))
|
||||
)
|
||||
.basic(
|
||||
"(1 + (2 + 3))",
|
||||
"1 + 2 + 3",
|
||||
v -> v(6),
|
||||
new Add(c(1), new Add(c(2), c(3)))
|
||||
)
|
||||
.basic(
|
||||
"((1 - 2) - 3)",
|
||||
"1 - 2 - 3",
|
||||
v -> v(-4),
|
||||
new Subtract(new Subtract(c(1), c(2)), c(3))
|
||||
)
|
||||
.basic(
|
||||
"(1 - (2 - 3))",
|
||||
"1 - (2 - 3)",
|
||||
v -> v(2),
|
||||
new Subtract(c(1), new Subtract(c(2), c(3)))
|
||||
)
|
||||
.basic(
|
||||
"((1 * 2) * 3)",
|
||||
"1 * 2 * 3",
|
||||
v -> v(6),
|
||||
new Multiply(new Multiply(c(1), c(2)), c(3))
|
||||
)
|
||||
.basic(
|
||||
"(1 * (2 * 3))",
|
||||
"1 * 2 * 3",
|
||||
v -> v(6),
|
||||
new Multiply(c(1), new Multiply(c(2), c(3)))
|
||||
)
|
||||
.basic(
|
||||
"((10 / 2) / 3)",
|
||||
"10 / 2 / 3",
|
||||
v -> v(10).divide(v(2)).divide(v(3)),
|
||||
new Divide(new Divide(c(10), c(2)), c(3))
|
||||
)
|
||||
.basic(
|
||||
"(10 / (3 / 2))",
|
||||
"10 / (3 / 2)",
|
||||
v -> v(10).divide(v(3).divide(v(2))),
|
||||
new Divide(c(10), new Divide(c(3), c(2)))
|
||||
)
|
||||
.basic(
|
||||
"(($x * $x) + (($x - 1) / 10))",
|
||||
"$x * $x + ($x - 1) / 10",
|
||||
v -> x(v).multiply(x(v)).add(x(v).subtract(v(1)).divide(v(10))),
|
||||
new Add(
|
||||
new Multiply(vx, vx),
|
||||
new Divide(new Subtract(vx, c(1)), c(10))
|
||||
)
|
||||
)
|
||||
.basic(
|
||||
"($x * -1000000000)",
|
||||
"$x * -1000000000",
|
||||
v -> x(v).multiply(v(-1_000_000_000)),
|
||||
new Multiply(vx, c(-1_000_000_000))
|
||||
)
|
||||
.basic(
|
||||
"($x * -1000000000000000)",
|
||||
"$x * -1000000000000000",
|
||||
v -> x(v).multiply(v(-1_000_000_000_000_000L)),
|
||||
new Multiply(vx, c(-1_000_000_000_000_000L))
|
||||
)
|
||||
.basic(
|
||||
"(10 / $x)",
|
||||
"10 / $x",
|
||||
v -> v(10).divide(x(v)),
|
||||
new Divide(c(10), vx)
|
||||
)
|
||||
.basic(
|
||||
"($x / $x)",
|
||||
"$x / $x",
|
||||
v -> x(v).divide(x(v)),
|
||||
new Divide(vx, vx)
|
||||
)
|
||||
.advanced("(2 + 1)", "2 + 1", v -> v(2 + 1), new Add(c(2), c(1)))
|
||||
.advanced(
|
||||
"($x - 1)",
|
||||
"$x - 1",
|
||||
v -> x(v).subtract(v(1)),
|
||||
new Subtract(vx, c(1))
|
||||
)
|
||||
.advanced(
|
||||
"(1 * 2)",
|
||||
"1 * 2",
|
||||
v -> v(1 * 2),
|
||||
new Multiply(c(1), c(2))
|
||||
)
|
||||
.advanced(
|
||||
"($x / 1)",
|
||||
"$x / 1",
|
||||
v -> x(v).divide(v(1)),
|
||||
new Divide(vx, c(1))
|
||||
)
|
||||
.advanced(
|
||||
"(1 + (2 + 1))",
|
||||
"1 + 2 + 1",
|
||||
v -> v(1 + 2 + 1),
|
||||
new Add(c(1), new Add(c(2), c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"($x - ($x - 1))",
|
||||
"$x - ($x - 1)",
|
||||
v -> x(v).subtract(x(v).subtract(v(1))),
|
||||
new Subtract(vx, new Subtract(vx, c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"(2 * ($x / 1))",
|
||||
"2 * ($x / 1)",
|
||||
v -> v(2).multiply(x(v).divide(v(1))),
|
||||
new Multiply(c(2), new Divide(vx, c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"(2 / ($x - 1))",
|
||||
"2 / ($x - 1)",
|
||||
v -> v(2).divide(x(v).subtract(v(1))),
|
||||
new Divide(c(2), new Subtract(vx, c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"((1 * 2) + $x)",
|
||||
"1 * 2 + $x",
|
||||
v -> v(1 * 2).add(x(v)),
|
||||
new Add(new Multiply(c(1), c(2)), vx)
|
||||
)
|
||||
.advanced(
|
||||
"(($x - 1) - 2)",
|
||||
"$x - 1 - 2",
|
||||
v -> x(v).subtract(v(3)),
|
||||
new Subtract(new Subtract(vx, c(1)), c(2))
|
||||
)
|
||||
.advanced(
|
||||
"(($x / 1) * 2)",
|
||||
"$x / 1 * 2",
|
||||
v -> x(v).multiply(v(2)),
|
||||
new Multiply(new Divide(vx, c(1)), c(2))
|
||||
)
|
||||
.advanced(
|
||||
"((2 + 1) / 1)",
|
||||
"(2 + 1) / 1",
|
||||
v -> v(3),
|
||||
new Divide(new Add(c(2), c(1)), c(1))
|
||||
)
|
||||
.advanced(
|
||||
"(1 + (1 + (2 + 1)))",
|
||||
"1 + 1 + 2 + 1",
|
||||
v -> v(1 + 1 + 2 + 1),
|
||||
new Add(c(1), new Add(c(1), new Add(c(2), c(1))))
|
||||
)
|
||||
.advanced(
|
||||
"($x - ((1 * 2) + $x))",
|
||||
"$x - (1 * 2 + $x)",
|
||||
v -> x(v).subtract(v(1 * 2).add(x(v))),
|
||||
new Subtract(vx, new Add(new Multiply(c(1), c(2)), vx))
|
||||
)
|
||||
.advanced(
|
||||
"($x * (2 / ($x - 1)))",
|
||||
"$x * (2 / ($x - 1))",
|
||||
v -> x(v).multiply(v(2).divide(x(v).subtract(v(1)))),
|
||||
new Multiply(vx, new Divide(c(2), new Subtract(vx, c(1))))
|
||||
)
|
||||
.advanced(
|
||||
"($x / (1 + (2 + 1)))",
|
||||
"$x / (1 + 2 + 1)",
|
||||
v -> x(v).divide(v(1 + 2 + 1)),
|
||||
new Divide(vx, new Add(c(1), new Add(c(2), c(1))))
|
||||
)
|
||||
.advanced(
|
||||
"((1 * 2) + (2 + 1))",
|
||||
"1 * 2 + 2 + 1",
|
||||
v -> v(1 * 2 + 2 + 1),
|
||||
new Add(new Multiply(c(1), c(2)), new Add(c(2), c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"((2 + 1) - (2 + 1))",
|
||||
"2 + 1 - (2 + 1)",
|
||||
v -> v(2 + 1 - (2 + 1)),
|
||||
new Subtract(new Add(c(2), c(1)), new Add(c(2), c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"(($x - 1) * ($x / 1))",
|
||||
"($x - 1) * ($x / 1)",
|
||||
v -> x(v).subtract(v(1)).multiply(x(v).divide(v(1))),
|
||||
new Multiply(new Subtract(vx, c(1)), new Divide(vx, c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"(($x - 1) / (1 * 2))",
|
||||
"($x - 1) / (1 * 2)",
|
||||
v -> x(v).subtract(v(1)).divide(v(2)),
|
||||
new Divide(new Subtract(vx, c(1)), new Multiply(c(1), c(2)))
|
||||
)
|
||||
.advanced(
|
||||
"((($x - 1) - 2) + $x)",
|
||||
"$x - 1 - 2 + $x",
|
||||
v -> x(v).subtract(v(3)).add(x(v)),
|
||||
new Add(new Subtract(new Subtract(vx, c(1)), c(2)), vx)
|
||||
)
|
||||
.advanced(
|
||||
"(((1 * 2) + $x) - 1)",
|
||||
"1 * 2 + $x - 1",
|
||||
v -> v(1).add(x(v)),
|
||||
new Subtract(new Add(new Multiply(c(1), c(2)), vx), c(1))
|
||||
)
|
||||
.advanced(
|
||||
"(((2 + 1) / 1) * $x)",
|
||||
"(2 + 1) / 1 * $x",
|
||||
v -> v(3).multiply(x(v)),
|
||||
new Multiply(new Divide(new Add(c(2), c(1)), c(1)), vx)
|
||||
)
|
||||
.advanced(
|
||||
"((2 / ($x - 1)) / 2)",
|
||||
"2 / ($x - 1) / 2",
|
||||
v -> v(2).divide(x(v).subtract(v(1))).divide(v(2)),
|
||||
new Divide(new Divide(c(2), new Subtract(vx, c(1))), c(2))
|
||||
);
|
||||
}
|
||||
|
||||
private static BigDecimal x(final List<BigDecimal> vars) {
|
||||
return vars.get(0);
|
||||
}
|
||||
|
||||
private static BigDecimal y(final List<BigDecimal> vars) {
|
||||
return vars.get(1);
|
||||
}
|
||||
|
||||
private static Const c(final BigDecimal v) {
|
||||
return TYPE.constant(v);
|
||||
}
|
||||
|
||||
private static Const c(final long v) {
|
||||
return TYPE.constant(v(v));
|
||||
}
|
||||
|
||||
private static BigDecimal v(final long v) {
|
||||
return BigDecimal.valueOf(v);
|
||||
}
|
||||
|
||||
static void main(final String... args) {
|
||||
TripleExpression.SELECTOR.variant(
|
||||
"BigDecimalList",
|
||||
ExpressionTest.v(BigDecimalListExpression::tester)
|
||||
).main(args);
|
||||
}
|
||||
}
|
||||
357
java/expression/BigIntegerListExpression.java
Normal file
357
java/expression/BigIntegerListExpression.java
Normal file
@@ -0,0 +1,357 @@
|
||||
package expression;
|
||||
|
||||
import base.Asserts;
|
||||
import base.Pair;
|
||||
import base.TestCounter;
|
||||
import expression.common.ExpressionKind;
|
||||
import expression.common.Type;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@SuppressWarnings("ClassReferencesSubclass")
|
||||
public interface BigIntegerListExpression extends ToMiniString {
|
||||
BigInteger evaluateBi(List<BigInteger> variables);
|
||||
|
||||
// Tests follow. You may temporarily remove everything til the end.
|
||||
|
||||
Add EXAMPLE = new Add(
|
||||
new Subtract(new Variable(0), new Const(BigInteger.ONE)),
|
||||
new Multiply(new Variable(1), new Const(BigInteger.TEN))
|
||||
);
|
||||
|
||||
Type<BigInteger> TYPE = new Type<>(
|
||||
BigInteger::valueOf,
|
||||
random -> v(random.getRandom().nextLong()),
|
||||
BigInteger.class
|
||||
);
|
||||
ExpressionKind<BigIntegerListExpression, BigInteger> KIND =
|
||||
new ExpressionKind<>(
|
||||
TYPE,
|
||||
BigIntegerListExpression.class,
|
||||
(r, c) ->
|
||||
IntStream.range(0, c)
|
||||
.mapToObj(name ->
|
||||
Pair.<String, BigIntegerListExpression>of(
|
||||
"$" + name,
|
||||
new Variable(name)
|
||||
)
|
||||
)
|
||||
.toList(),
|
||||
(expr, variables, values) -> expr.evaluateBi(values)
|
||||
);
|
||||
|
||||
@SuppressWarnings("PointlessArithmeticExpression")
|
||||
static ExpressionTester<?, ?> tester(final TestCounter counter) {
|
||||
Asserts.assertEquals(
|
||||
"Example toString()",
|
||||
"(($0 - 1) + ($1 * 10))",
|
||||
EXAMPLE.toString()
|
||||
);
|
||||
Asserts.assertEquals(
|
||||
EXAMPLE + " at (2, 3)",
|
||||
BigInteger.valueOf(31),
|
||||
EXAMPLE.evaluateBi(
|
||||
List.of(BigInteger.valueOf(2), BigInteger.valueOf(3))
|
||||
)
|
||||
);
|
||||
|
||||
final Variable vx = new Variable(0);
|
||||
final Variable vy = new Variable(1);
|
||||
|
||||
return new ExpressionTester<>(
|
||||
counter,
|
||||
KIND,
|
||||
c -> v -> c,
|
||||
(op, a, b) -> v -> op.apply(a.evaluateBi(v), b.evaluateBi(v)),
|
||||
BigInteger::add,
|
||||
BigInteger::subtract,
|
||||
BigInteger::multiply,
|
||||
BigInteger::divide
|
||||
)
|
||||
.basic("10", "10", v -> v(10), c(10))
|
||||
.basic("$x", "$x", BigIntegerListExpression::x, vx)
|
||||
.basic("$y", "$y", BigIntegerListExpression::y, vy)
|
||||
.basic("($x + $y)", "$x + $y", v -> x(v).add(y(v)), new Add(vx, vy))
|
||||
.basic("($x + 2)", "$x + 2", v -> x(v).add(v(2)), new Add(vx, c(2)))
|
||||
.basic(
|
||||
"(2 - $x)",
|
||||
"2 - $x",
|
||||
v -> v(2).subtract(x(v)),
|
||||
new Subtract(c(2), vx)
|
||||
)
|
||||
.basic(
|
||||
"(3 * $x)",
|
||||
"3 * $x",
|
||||
v -> v(3).multiply(x(v)),
|
||||
new Multiply(c(3), vx)
|
||||
)
|
||||
.basic("($x + $x)", "$x + $x", v -> x(v).add(x(v)), new Add(vx, vx))
|
||||
.basic(
|
||||
"($x / -2)",
|
||||
"$x / -2",
|
||||
v -> x(v).divide(v(-2)),
|
||||
new Divide(vx, c(-2))
|
||||
)
|
||||
.basic("(2 + $x)", "2 + $x", v -> v(2).add(x(v)), new Add(c(2), vx))
|
||||
.basic(
|
||||
"((1 + 2) + 3)",
|
||||
"1 + 2 + 3",
|
||||
v -> v(6),
|
||||
new Add(new Add(c(1), c(2)), c(3))
|
||||
)
|
||||
.basic(
|
||||
"(1 + (2 * 3))",
|
||||
"1 + 2 * 3",
|
||||
v -> v(7),
|
||||
new Add(c(1), new Multiply(c(2), c(3)))
|
||||
)
|
||||
.basic(
|
||||
"(1 - (2 * 3))",
|
||||
"1 - 2 * 3",
|
||||
v -> v(-5),
|
||||
new Subtract(c(1), new Multiply(c(2), c(3)))
|
||||
)
|
||||
.basic(
|
||||
"(1 + (2 + 3))",
|
||||
"1 + 2 + 3",
|
||||
v -> v(6),
|
||||
new Add(c(1), new Add(c(2), c(3)))
|
||||
)
|
||||
.basic(
|
||||
"((1 - 2) - 3)",
|
||||
"1 - 2 - 3",
|
||||
v -> v(-4),
|
||||
new Subtract(new Subtract(c(1), c(2)), c(3))
|
||||
)
|
||||
.basic(
|
||||
"(1 - (2 - 3))",
|
||||
"1 - (2 - 3)",
|
||||
v -> v(2),
|
||||
new Subtract(c(1), new Subtract(c(2), c(3)))
|
||||
)
|
||||
.basic(
|
||||
"((1 * 2) * 3)",
|
||||
"1 * 2 * 3",
|
||||
v -> v(6),
|
||||
new Multiply(new Multiply(c(1), c(2)), c(3))
|
||||
)
|
||||
.basic(
|
||||
"(1 * (2 * 3))",
|
||||
"1 * 2 * 3",
|
||||
v -> v(6),
|
||||
new Multiply(c(1), new Multiply(c(2), c(3)))
|
||||
)
|
||||
.basic(
|
||||
"((10 / 2) / 3)",
|
||||
"10 / 2 / 3",
|
||||
v -> v(10 / 2 / 3),
|
||||
new Divide(new Divide(c(10), c(2)), c(3))
|
||||
)
|
||||
.basic(
|
||||
"(10 / (3 / 2))",
|
||||
"10 / (3 / 2)",
|
||||
v -> v(10 / (3 / 2)),
|
||||
new Divide(c(10), new Divide(c(3), c(2)))
|
||||
)
|
||||
.basic(
|
||||
"(($x * $x) + (($x - 1) / 10))",
|
||||
"$x * $x + ($x - 1) / 10",
|
||||
v -> x(v).multiply(x(v)).add(x(v).subtract(v(1)).divide(v(10))),
|
||||
new Add(
|
||||
new Multiply(vx, vx),
|
||||
new Divide(new Subtract(vx, c(1)), c(10))
|
||||
)
|
||||
)
|
||||
.basic(
|
||||
"($x * -1000000000)",
|
||||
"$x * -1000000000",
|
||||
v -> x(v).multiply(v(-1_000_000_000)),
|
||||
new Multiply(vx, c(-1_000_000_000))
|
||||
)
|
||||
.basic(
|
||||
"($x * -1000000000000000)",
|
||||
"$x * -1000000000000000",
|
||||
v -> x(v).multiply(v(-1_000_000_000_000_000L)),
|
||||
new Multiply(vx, c(-1_000_000_000_000_000L))
|
||||
)
|
||||
.basic(
|
||||
"(10 / $x)",
|
||||
"10 / $x",
|
||||
v -> v(10).divide(x(v)),
|
||||
new Divide(c(10), vx)
|
||||
)
|
||||
.basic(
|
||||
"($x / $x)",
|
||||
"$x / $x",
|
||||
v -> x(v).divide(x(v)),
|
||||
new Divide(vx, vx)
|
||||
)
|
||||
.advanced("(2 + 1)", "2 + 1", v -> v(2 + 1), new Add(c(2), c(1)))
|
||||
.advanced(
|
||||
"($x - 1)",
|
||||
"$x - 1",
|
||||
v -> x(v).subtract(v(1)),
|
||||
new Subtract(vx, c(1))
|
||||
)
|
||||
.advanced(
|
||||
"(1 * 2)",
|
||||
"1 * 2",
|
||||
v -> v(1 * 2),
|
||||
new Multiply(c(1), c(2))
|
||||
)
|
||||
.advanced(
|
||||
"($x / 1)",
|
||||
"$x / 1",
|
||||
v -> x(v).divide(v(1)),
|
||||
new Divide(vx, c(1))
|
||||
)
|
||||
.advanced(
|
||||
"(1 + (2 + 1))",
|
||||
"1 + 2 + 1",
|
||||
v -> v(1 + 2 + 1),
|
||||
new Add(c(1), new Add(c(2), c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"($x - ($x - 1))",
|
||||
"$x - ($x - 1)",
|
||||
v -> x(v).subtract(x(v).subtract(v(1))),
|
||||
new Subtract(vx, new Subtract(vx, c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"(2 * ($x / 1))",
|
||||
"2 * ($x / 1)",
|
||||
v -> v(2).multiply(x(v).divide(v(1))),
|
||||
new Multiply(c(2), new Divide(vx, c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"(2 / ($x - 1))",
|
||||
"2 / ($x - 1)",
|
||||
v -> v(2).divide(x(v).subtract(v(1))),
|
||||
new Divide(c(2), new Subtract(vx, c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"((1 * 2) + $x)",
|
||||
"1 * 2 + $x",
|
||||
v -> v(1 * 2).add(x(v)),
|
||||
new Add(new Multiply(c(1), c(2)), vx)
|
||||
)
|
||||
.advanced(
|
||||
"(($x - 1) - 2)",
|
||||
"$x - 1 - 2",
|
||||
v -> x(v).subtract(v(3)),
|
||||
new Subtract(new Subtract(vx, c(1)), c(2))
|
||||
)
|
||||
.advanced(
|
||||
"(($x / 1) * 2)",
|
||||
"$x / 1 * 2",
|
||||
v -> x(v).multiply(v(2)),
|
||||
new Multiply(new Divide(vx, c(1)), c(2))
|
||||
)
|
||||
.advanced(
|
||||
"((2 + 1) / 1)",
|
||||
"(2 + 1) / 1",
|
||||
v -> v(3),
|
||||
new Divide(new Add(c(2), c(1)), c(1))
|
||||
)
|
||||
.advanced(
|
||||
"(1 + (1 + (2 + 1)))",
|
||||
"1 + 1 + 2 + 1",
|
||||
v -> v(1 + 1 + 2 + 1),
|
||||
new Add(c(1), new Add(c(1), new Add(c(2), c(1))))
|
||||
)
|
||||
.advanced(
|
||||
"($x - ((1 * 2) + $x))",
|
||||
"$x - (1 * 2 + $x)",
|
||||
v -> x(v).subtract(v(1 * 2).add(x(v))),
|
||||
new Subtract(vx, new Add(new Multiply(c(1), c(2)), vx))
|
||||
)
|
||||
.advanced(
|
||||
"($x * (2 / ($x - 1)))",
|
||||
"$x * (2 / ($x - 1))",
|
||||
v -> x(v).multiply(v(2).divide(x(v).subtract(v(1)))),
|
||||
new Multiply(vx, new Divide(c(2), new Subtract(vx, c(1))))
|
||||
)
|
||||
.advanced(
|
||||
"($x / (1 + (2 + 1)))",
|
||||
"$x / (1 + 2 + 1)",
|
||||
v -> x(v).divide(v(1 + 2 + 1)),
|
||||
new Divide(vx, new Add(c(1), new Add(c(2), c(1))))
|
||||
)
|
||||
.advanced(
|
||||
"((1 * 2) + (2 + 1))",
|
||||
"1 * 2 + 2 + 1",
|
||||
v -> v(1 * 2 + 2 + 1),
|
||||
new Add(new Multiply(c(1), c(2)), new Add(c(2), c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"((2 + 1) - (2 + 1))",
|
||||
"2 + 1 - (2 + 1)",
|
||||
v -> v(2 + 1 - (2 + 1)),
|
||||
new Subtract(new Add(c(2), c(1)), new Add(c(2), c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"(($x - 1) * ($x / 1))",
|
||||
"($x - 1) * ($x / 1)",
|
||||
v -> x(v).subtract(v(1)).multiply(x(v).divide(v(1))),
|
||||
new Multiply(new Subtract(vx, c(1)), new Divide(vx, c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"(($x - 1) / (1 * 2))",
|
||||
"($x - 1) / (1 * 2)",
|
||||
v -> x(v).subtract(v(1)).divide(v(2)),
|
||||
new Divide(new Subtract(vx, c(1)), new Multiply(c(1), c(2)))
|
||||
)
|
||||
.advanced(
|
||||
"((($x - 1) - 2) + $x)",
|
||||
"$x - 1 - 2 + $x",
|
||||
v -> x(v).subtract(v(3)).add(x(v)),
|
||||
new Add(new Subtract(new Subtract(vx, c(1)), c(2)), vx)
|
||||
)
|
||||
.advanced(
|
||||
"(((1 * 2) + $x) - 1)",
|
||||
"1 * 2 + $x - 1",
|
||||
v -> v(1).add(x(v)),
|
||||
new Subtract(new Add(new Multiply(c(1), c(2)), vx), c(1))
|
||||
)
|
||||
.advanced(
|
||||
"(((2 + 1) / 1) * $x)",
|
||||
"(2 + 1) / 1 * $x",
|
||||
v -> v(3).multiply(x(v)),
|
||||
new Multiply(new Divide(new Add(c(2), c(1)), c(1)), vx)
|
||||
)
|
||||
.advanced(
|
||||
"((2 / ($x - 1)) / 2)",
|
||||
"2 / ($x - 1) / 2",
|
||||
v -> v(2).divide(x(v).subtract(v(1))).divide(v(2)),
|
||||
new Divide(new Divide(c(2), new Subtract(vx, c(1))), c(2))
|
||||
);
|
||||
}
|
||||
|
||||
private static BigInteger x(final List<BigInteger> vars) {
|
||||
return vars.get(0);
|
||||
}
|
||||
|
||||
private static BigInteger y(final List<BigInteger> vars) {
|
||||
return vars.get(1);
|
||||
}
|
||||
|
||||
private static Const c(final long v) {
|
||||
return TYPE.constant(v(v));
|
||||
}
|
||||
|
||||
private static BigInteger v(final long v) {
|
||||
return BigInteger.valueOf(v);
|
||||
}
|
||||
|
||||
static void main(final String... args) {
|
||||
TripleExpression.SELECTOR.variant(
|
||||
"BigIntegerList",
|
||||
ExpressionTest.v(BigIntegerListExpression::tester)
|
||||
).main(args);
|
||||
}
|
||||
}
|
||||
62
java/expression/Cbrt.java
Normal file
62
java/expression/Cbrt.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package expression;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class Cbrt extends AbstractExpression {
|
||||
private final AbstractExpression operand;
|
||||
|
||||
public Cbrt(AbstractExpression operand) {
|
||||
this.operand = operand;
|
||||
}
|
||||
|
||||
private static int cbrtInt(int n) {
|
||||
if (n == 0) return 0;
|
||||
boolean negative = n < 0;
|
||||
int abs = (n == Integer.MIN_VALUE) ? Integer.MAX_VALUE : (negative ? -n : n);
|
||||
|
||||
int lo = 0, hi = 1290;
|
||||
while (lo < hi) {
|
||||
int mid = (lo + hi + 1) / 2;
|
||||
if (mid * mid * mid <= abs) {
|
||||
lo = mid;
|
||||
} else {
|
||||
hi = mid - 1;
|
||||
}
|
||||
}
|
||||
return negative ? -lo : lo;
|
||||
}
|
||||
|
||||
@Override public int evaluate(int x) { return cbrtInt(operand.evaluate(x)); }
|
||||
@Override public int evaluate(int x, int y, int z) { return cbrtInt(operand.evaluate(x,y,z)); }
|
||||
@Override public int evaluate(List<Integer> vars) { return cbrtInt(operand.evaluate(vars)); }
|
||||
@Override public BigInteger evaluateBi(List<BigInteger> vars) {
|
||||
throw new UnsupportedOperationException("∛ not supported for BigInteger");
|
||||
}
|
||||
@Override public BigDecimal evaluateBd(List<BigDecimal> vars) {
|
||||
throw new UnsupportedOperationException("∛ not supported for BigDecimal");
|
||||
}
|
||||
|
||||
@Override public String toString() { return "∛(" + operand + ")"; }
|
||||
|
||||
@Override
|
||||
public String toMiniString() {
|
||||
if (operand instanceof AbstractBinaryOperation) {
|
||||
return "∛(" + operand.toMiniString() + ")";
|
||||
}
|
||||
return "∛" + operand.toMiniString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Cbrt)) return false;
|
||||
return operand.equals(((Cbrt) obj).operand);
|
||||
}
|
||||
|
||||
@Override public int hashCode() { return operand.hashCode() ^ 0x43425254; }
|
||||
}
|
||||
75
java/expression/Ceiling.java
Normal file
75
java/expression/Ceiling.java
Normal file
@@ -0,0 +1,75 @@
|
||||
package expression;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class Ceiling extends AbstractExpression {
|
||||
|
||||
private final AbstractExpression operand;
|
||||
|
||||
public Ceiling(AbstractExpression operand) {
|
||||
this.operand = operand;
|
||||
}
|
||||
|
||||
private static int ceilInt(int n) {
|
||||
return Math.ceilDiv(n, 1000) * 1000;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int evaluate(int x) {
|
||||
return ceilInt(operand.evaluate(x));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int evaluate(int x, int y, int z) {
|
||||
return ceilInt(operand.evaluate(x, y, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int evaluate(List<Integer> vars) {
|
||||
return ceilInt(operand.evaluate(vars));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger evaluateBi(List<BigInteger> vars) {
|
||||
throw new UnsupportedOperationException(
|
||||
"ceiling not supported for BigInteger"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal evaluateBd(List<BigDecimal> vars) {
|
||||
throw new UnsupportedOperationException(
|
||||
"ceiling not supported for BigDecimal"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ceiling(" + operand + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toMiniString() {
|
||||
if (operand instanceof AbstractBinaryOperation) {
|
||||
return "ceiling(" + operand.toMiniString() + ")";
|
||||
}
|
||||
return "ceiling " + operand.toMiniString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Ceiling)) return false;
|
||||
return operand.equals(((Ceiling) obj).operand);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return operand.hashCode() ^ 0x43454C47;
|
||||
}
|
||||
}
|
||||
46
java/expression/Clear.java
Normal file
46
java/expression/Clear.java
Normal file
@@ -0,0 +1,46 @@
|
||||
package expression;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class Clear extends AbstractBinaryOperation {
|
||||
|
||||
public Clear(AbstractExpression l, AbstractExpression r) {
|
||||
super(l, r);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getOperator() {
|
||||
return "clear";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPriority() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isRightAssoc() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int applyInt(int a, int b) {
|
||||
return a & ~(1 << b);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BigInteger applyBi(BigInteger a, BigInteger b) {
|
||||
return a.clearBit(b.intValueExact());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BigDecimal applyBd(BigDecimal a, BigDecimal b) {
|
||||
throw new UnsupportedOperationException(
|
||||
"clear not supported for BigDecimal"
|
||||
);
|
||||
}
|
||||
}
|
||||
84
java/expression/Const.java
Normal file
84
java/expression/Const.java
Normal file
@@ -0,0 +1,84 @@
|
||||
package expression;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class Const extends AbstractExpression {
|
||||
|
||||
private final int intValue;
|
||||
private final BigInteger biValue;
|
||||
private final BigDecimal bdValue;
|
||||
private final String repr;
|
||||
|
||||
public Const(int value) {
|
||||
this.intValue = value;
|
||||
this.biValue = BigInteger.valueOf(value);
|
||||
this.bdValue = BigDecimal.valueOf(value);
|
||||
this.repr = Integer.toString(value);
|
||||
}
|
||||
|
||||
public Const(BigInteger value) {
|
||||
this.intValue = value.intValueExact();
|
||||
this.biValue = value;
|
||||
this.bdValue = new BigDecimal(value);
|
||||
this.repr = value.toString();
|
||||
}
|
||||
|
||||
public Const(BigDecimal value) {
|
||||
this.bdValue = value;
|
||||
this.biValue = value.toBigIntegerExact();
|
||||
this.intValue = biValue.intValueExact();
|
||||
this.repr = value.toPlainString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int evaluate(int x) {
|
||||
return intValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int evaluate(int x, int y, int z) {
|
||||
return intValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int evaluate(List<Integer> vars) {
|
||||
return intValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger evaluateBi(List<BigInteger> vars) {
|
||||
return biValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal evaluateBd(List<BigDecimal> vars) {
|
||||
return bdValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return repr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toMiniString() {
|
||||
return repr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Const)) return false;
|
||||
return repr.equals(((Const) obj).repr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return repr.hashCode();
|
||||
}
|
||||
}
|
||||
55
java/expression/Cube.java
Normal file
55
java/expression/Cube.java
Normal file
@@ -0,0 +1,55 @@
|
||||
package expression;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class Cube extends AbstractExpression {
|
||||
private final AbstractExpression operand;
|
||||
|
||||
public Cube(AbstractExpression operand) {
|
||||
this.operand = operand;
|
||||
}
|
||||
|
||||
private static int checkedMul(int a, int b) {
|
||||
if (a == 0 || b == 0) return 0;
|
||||
if (a == Integer.MIN_VALUE || b == Integer.MIN_VALUE) throw new OverflowException("cube");
|
||||
int r = a * b;
|
||||
if (r / a != b) throw new OverflowException("cube");
|
||||
return r;
|
||||
}
|
||||
|
||||
private static int cubeInt(int n) {
|
||||
return checkedMul(checkedMul(n, n), n);
|
||||
}
|
||||
|
||||
@Override public int evaluate(int x) { return cubeInt(operand.evaluate(x)); }
|
||||
@Override public int evaluate(int x, int y, int z) { return cubeInt(operand.evaluate(x,y,z)); }
|
||||
@Override public int evaluate(List<Integer> vars) { return cubeInt(operand.evaluate(vars)); }
|
||||
@Override public BigInteger evaluateBi(List<BigInteger> vars) { return operand.evaluateBi(vars).pow(3); }
|
||||
@Override public BigDecimal evaluateBd(List<BigDecimal> vars) { return operand.evaluateBd(vars).pow(3); }
|
||||
|
||||
@Override public String toString() { return "(" + operand + ")³"; }
|
||||
|
||||
@Override
|
||||
public String toMiniString() {
|
||||
String s = operand.toMiniString();
|
||||
boolean needParens = operand instanceof AbstractBinaryOperation
|
||||
|| operand instanceof Cbrt
|
||||
|| operand instanceof Sqrt
|
||||
|| (s.startsWith("-") && (s.length() < 2 || !Character.isDigit(s.charAt(1))));
|
||||
return needParens ? "(" + s + ")³" : s + "³";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Cube)) return false;
|
||||
return operand.equals(((Cube) obj).operand);
|
||||
}
|
||||
|
||||
@Override public int hashCode() { return operand.hashCode() ^ 0x43554245; }
|
||||
}
|
||||
83
java/expression/Digits.java
Normal file
83
java/expression/Digits.java
Normal file
@@ -0,0 +1,83 @@
|
||||
package expression;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class Digits extends AbstractExpression {
|
||||
|
||||
private final AbstractExpression operand;
|
||||
|
||||
public Digits(AbstractExpression operand) {
|
||||
this.operand = operand;
|
||||
}
|
||||
|
||||
private static int digitsSum(int n) {
|
||||
if (n == 0) return 0;
|
||||
boolean negative = n < 0;
|
||||
long abs = Math.abs((long) n);
|
||||
int sum = 0;
|
||||
while (abs > 0) {
|
||||
sum += (int) (abs % 10);
|
||||
abs /= 10;
|
||||
}
|
||||
return negative ? -sum : sum;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int evaluate(int x) {
|
||||
return digitsSum(operand.evaluate(x));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int evaluate(int x, int y, int z) {
|
||||
return digitsSum(operand.evaluate(x, y, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int evaluate(List<Integer> vars) {
|
||||
return digitsSum(operand.evaluate(vars));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger evaluateBi(List<BigInteger> vars) {
|
||||
throw new UnsupportedOperationException(
|
||||
"digits not supported for BigInteger"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal evaluateBd(List<BigDecimal> vars) {
|
||||
throw new UnsupportedOperationException(
|
||||
"digits not supported for BigDecimal"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "digits(" + operand + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toMiniString() {
|
||||
if (operand instanceof AbstractBinaryOperation) {
|
||||
return "digits(" + operand.toMiniString() + ")";
|
||||
}
|
||||
return "digits " + operand.toMiniString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Digits)) return false;
|
||||
return operand.equals(((Digits) obj).operand);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return operand.hashCode() ^ 0x44494754;
|
||||
}
|
||||
}
|
||||
45
java/expression/Divide.java
Normal file
45
java/expression/Divide.java
Normal file
@@ -0,0 +1,45 @@
|
||||
package expression;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.math.MathContext;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class Divide extends AbstractBinaryOperation {
|
||||
|
||||
public Divide(AbstractExpression l, AbstractExpression r) {
|
||||
super(l, r);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getOperator() {
|
||||
return "/";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPriority() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isRightAssoc() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int applyInt(int a, int b) {
|
||||
return a / b;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BigInteger applyBi(BigInteger a, BigInteger b) {
|
||||
return a.divide(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BigDecimal applyBd(BigDecimal a, BigDecimal b) {
|
||||
return a.divide(b, MathContext.DECIMAL128);
|
||||
}
|
||||
}
|
||||
10
java/expression/DivisionByZeroException.java
Normal file
10
java/expression/DivisionByZeroException.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package expression;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class DivisionByZeroException extends ArithmeticException {
|
||||
public DivisionByZeroException() {
|
||||
super("division by zero");
|
||||
}
|
||||
}
|
||||
285
java/expression/Expression.java
Normal file
285
java/expression/Expression.java
Normal file
@@ -0,0 +1,285 @@
|
||||
package expression;
|
||||
|
||||
import base.Asserts;
|
||||
import base.ExtendedRandom;
|
||||
import base.Pair;
|
||||
import base.TestCounter;
|
||||
import expression.common.ExpressionKind;
|
||||
import expression.common.Type;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* One-argument arithmetic expression over integers.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@SuppressWarnings("ClassReferencesSubclass")
|
||||
public interface Expression extends ToMiniString {
|
||||
int evaluate(int x);
|
||||
|
||||
// Tests follow. You may temporarily remove everything til the end.
|
||||
|
||||
Subtract EXAMPLE = new Subtract(
|
||||
new Multiply(new Const(2), new Variable("x")),
|
||||
new Const(3)
|
||||
);
|
||||
|
||||
Type<Integer> TYPE = new Type<>(a -> a, ExtendedRandom::nextInt, int.class);
|
||||
ExpressionKind<Expression, Integer> KIND = new ExpressionKind<>(
|
||||
TYPE,
|
||||
Expression.class,
|
||||
List.of(Pair.of("x", new Variable("x"))),
|
||||
(expr, variables, values) -> expr.evaluate(values.get(0))
|
||||
);
|
||||
|
||||
private static Const c(final int c) {
|
||||
return new Const(c);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "PointlessArithmeticExpression", "Convert2MethodRef" })
|
||||
static ExpressionTester<?, ?> tester(final TestCounter counter) {
|
||||
Asserts.assertEquals(
|
||||
"Example toString()",
|
||||
"((2 * x) - 3)",
|
||||
EXAMPLE.toString()
|
||||
);
|
||||
Asserts.assertEquals("Example at 5", 7, EXAMPLE.evaluate(5));
|
||||
Asserts.assertTrue(
|
||||
"Example equals 1",
|
||||
new Multiply(new Const(2), new Variable("x")).equals(
|
||||
new Multiply(new Const(2), new Variable("x"))
|
||||
)
|
||||
);
|
||||
Asserts.assertTrue(
|
||||
"Example equals 2",
|
||||
!new Multiply(new Const(2), new Variable("x")).equals(
|
||||
new Multiply(new Variable("x"), new Const(2))
|
||||
)
|
||||
);
|
||||
|
||||
final Variable vx = new Variable("x");
|
||||
final Const c1 = c(1);
|
||||
final Const c2 = c(2);
|
||||
|
||||
return new ExpressionTester<>(
|
||||
counter,
|
||||
KIND,
|
||||
c -> x -> c,
|
||||
(op, a, b) -> x -> op.apply(a.evaluate(x), b.evaluate(x)),
|
||||
(a, b) -> a + b,
|
||||
(a, b) -> a - b,
|
||||
(a, b) -> a * b,
|
||||
(a, b) -> a / b
|
||||
)
|
||||
.basic("10", "10", x -> 10, c(10))
|
||||
.basic("x", "x", x -> x, vx)
|
||||
.basic("(x + 2)", "x + 2", x -> x + 2, new Add(vx, c(2)))
|
||||
.basic("(2 - x)", "2 - x", x -> 2 - x, new Subtract(c(2), vx))
|
||||
.basic("(3 * x)", "3 * x", x -> 3 * x, new Multiply(c(3), vx))
|
||||
.basic("(x + x)", "x + x", x -> x + x, new Add(vx, vx))
|
||||
.basic("(x / -2)", "x / -2", x -> -x / 2, new Divide(vx, c(-2)))
|
||||
.basic("(2 + x)", "2 + x", x -> 2 + x, new Add(c(2), vx))
|
||||
.basic(
|
||||
"((1 + 2) + 3)",
|
||||
"1 + 2 + 3",
|
||||
x -> 6,
|
||||
new Add(new Add(c(1), c(2)), c(3))
|
||||
)
|
||||
.basic(
|
||||
"(1 + (2 + 3))",
|
||||
"1 + 2 + 3",
|
||||
x -> 6,
|
||||
new Add(c(1), new Add(c(2), c(3)))
|
||||
)
|
||||
.basic(
|
||||
"((1 - 2) - 3)",
|
||||
"1 - 2 - 3",
|
||||
x -> -4,
|
||||
new Subtract(new Subtract(c(1), c(2)), c(3))
|
||||
)
|
||||
.basic(
|
||||
"(1 - (2 - 3))",
|
||||
"1 - (2 - 3)",
|
||||
x -> 2,
|
||||
new Subtract(c(1), new Subtract(c(2), c(3)))
|
||||
)
|
||||
.basic(
|
||||
"((1 * 2) * 3)",
|
||||
"1 * 2 * 3",
|
||||
x -> 6,
|
||||
new Multiply(new Multiply(c(1), c(2)), c(3))
|
||||
)
|
||||
.basic(
|
||||
"(1 * (2 * 3))",
|
||||
"1 * 2 * 3",
|
||||
x -> 6,
|
||||
new Multiply(c(1), new Multiply(c(2), c(3)))
|
||||
)
|
||||
.basic(
|
||||
"((10 / 2) / 3)",
|
||||
"10 / 2 / 3",
|
||||
x -> 10 / 2 / 3,
|
||||
new Divide(new Divide(c(10), c(2)), c(3))
|
||||
)
|
||||
.basic(
|
||||
"(10 / (3 / 2))",
|
||||
"10 / (3 / 2)",
|
||||
x -> 10 / (3 / 2),
|
||||
new Divide(c(10), new Divide(c(3), c(2)))
|
||||
)
|
||||
.basic(
|
||||
"(10 * (3 / 2))",
|
||||
"10 * (3 / 2)",
|
||||
x -> 10 * (3 / 2),
|
||||
new Multiply(c(10), new Divide(c(3), c(2)))
|
||||
)
|
||||
.basic(
|
||||
"(10 + (3 - 2))",
|
||||
"10 + 3 - 2",
|
||||
x -> 10 + (3 - 2),
|
||||
new Add(c(10), new Subtract(c(3), c(2)))
|
||||
)
|
||||
.basic(
|
||||
"((x * x) + ((x - 1) / 10))",
|
||||
"x * x + (x - 1) / 10",
|
||||
x -> x * x + (x - 1) / 10,
|
||||
new Add(
|
||||
new Multiply(vx, vx),
|
||||
new Divide(new Subtract(vx, c(1)), c(10))
|
||||
)
|
||||
)
|
||||
.basic(
|
||||
"(x * -1000000000)",
|
||||
"x * -1000000000",
|
||||
x -> x * -1_000_000_000,
|
||||
new Multiply(vx, c(-1_000_000_000))
|
||||
)
|
||||
.basic("(10 / x)", "10 / x", x -> 10 / x, new Divide(c(10), vx))
|
||||
.basic("(x / x)", "x / x", x -> x / x, new Divide(vx, vx))
|
||||
.advanced("(2 + 1)", "2 + 1", x -> 2 + 1, new Add(c2, c1))
|
||||
.advanced("(x - 1)", "x - 1", x -> x - 1, new Subtract(vx, c1))
|
||||
.advanced("(1 * 2)", "1 * 2", x -> 1 * 2, new Multiply(c1, c2))
|
||||
.advanced("(x / 1)", "x / 1", x -> x / 1, new Divide(vx, c1))
|
||||
.advanced(
|
||||
"(1 + (2 + 1))",
|
||||
"1 + 2 + 1",
|
||||
x -> 1 + 2 + 1,
|
||||
new Add(c1, new Add(c2, c1))
|
||||
)
|
||||
.advanced(
|
||||
"(x - (x - 1))",
|
||||
"x - (x - 1)",
|
||||
x -> x - (x - 1),
|
||||
new Subtract(vx, new Subtract(vx, c1))
|
||||
)
|
||||
.advanced(
|
||||
"(2 * (x / 1))",
|
||||
"2 * (x / 1)",
|
||||
x -> 2 * (x / 1),
|
||||
new Multiply(c2, new Divide(vx, c1))
|
||||
)
|
||||
.advanced(
|
||||
"(2 / (x - 1))",
|
||||
"2 / (x - 1)",
|
||||
x -> 2 / (x - 1),
|
||||
new Divide(c2, new Subtract(vx, c1))
|
||||
)
|
||||
.advanced(
|
||||
"((1 * 2) + x)",
|
||||
"1 * 2 + x",
|
||||
x -> 1 * 2 + x,
|
||||
new Add(new Multiply(c1, c2), vx)
|
||||
)
|
||||
.advanced(
|
||||
"((x - 1) - 2)",
|
||||
"x - 1 - 2",
|
||||
x -> x - 1 - 2,
|
||||
new Subtract(new Subtract(vx, c1), c2)
|
||||
)
|
||||
.advanced(
|
||||
"((x / 1) * 2)",
|
||||
"x / 1 * 2",
|
||||
x -> (x / 1) * 2,
|
||||
new Multiply(new Divide(vx, c1), c2)
|
||||
)
|
||||
.advanced(
|
||||
"((2 + 1) / 1)",
|
||||
"(2 + 1) / 1",
|
||||
x -> (2 + 1) / 1,
|
||||
new Divide(new Add(c2, c1), c1)
|
||||
)
|
||||
.advanced(
|
||||
"(1 + (1 + (2 + 1)))",
|
||||
"1 + 1 + 2 + 1",
|
||||
x -> 1 + 1 + 2 + 1,
|
||||
new Add(c1, new Add(c1, new Add(c2, c1)))
|
||||
)
|
||||
.advanced(
|
||||
"(x - ((1 * 2) + x))",
|
||||
"x - (1 * 2 + x)",
|
||||
x -> x - (1 * 2 + x),
|
||||
new Subtract(vx, new Add(new Multiply(c1, c2), vx))
|
||||
)
|
||||
.advanced(
|
||||
"(x * (2 / (x - 1)))",
|
||||
"x * (2 / (x - 1))",
|
||||
x -> x * (2 / (x - 1)),
|
||||
new Multiply(vx, new Divide(c2, new Subtract(vx, c1)))
|
||||
)
|
||||
.advanced(
|
||||
"(x / (1 + (2 + 1)))",
|
||||
"x / (1 + 2 + 1)",
|
||||
x -> x / (1 + 2 + 1),
|
||||
new Divide(vx, new Add(c1, new Add(c2, c1)))
|
||||
)
|
||||
.advanced(
|
||||
"((1 * 2) + (2 + 1))",
|
||||
"1 * 2 + 2 + 1",
|
||||
x -> 1 * 2 + 2 + 1,
|
||||
new Add(new Multiply(c1, c2), new Add(c2, c1))
|
||||
)
|
||||
.advanced(
|
||||
"((2 + 1) - (2 + 1))",
|
||||
"2 + 1 - (2 + 1)",
|
||||
x -> 2 + 1 - (2 + 1),
|
||||
new Subtract(new Add(c2, c1), new Add(c2, c1))
|
||||
)
|
||||
.advanced(
|
||||
"((x - 1) * (x / 1))",
|
||||
"(x - 1) * (x / 1)",
|
||||
x -> (x - 1) * (x / 1),
|
||||
new Multiply(new Subtract(vx, c1), new Divide(vx, c1))
|
||||
)
|
||||
.advanced(
|
||||
"((x - 1) / (1 * 2))",
|
||||
"(x - 1) / (1 * 2)",
|
||||
x -> (x - 1) / (1 * 2),
|
||||
new Divide(new Subtract(vx, c1), new Multiply(c1, c2))
|
||||
)
|
||||
.advanced(
|
||||
"(((x - 1) - 2) + x)",
|
||||
"x - 1 - 2 + x",
|
||||
x -> x - 1 - 2 + x,
|
||||
new Add(new Subtract(new Subtract(vx, c1), c2), vx)
|
||||
)
|
||||
.advanced(
|
||||
"(((1 * 2) + x) - 1)",
|
||||
"1 * 2 + x - 1",
|
||||
x -> 1 * 2 + x - 1,
|
||||
new Subtract(new Add(new Multiply(c1, c2), vx), c1)
|
||||
)
|
||||
.advanced(
|
||||
"(((2 + 1) / 1) * x)",
|
||||
"(2 + 1) / 1 * x",
|
||||
x -> ((2 + 1) / 1) * x,
|
||||
new Multiply(new Divide(new Add(c2, c1), c1), vx)
|
||||
)
|
||||
.advanced(
|
||||
"((2 / (x - 1)) / 2)",
|
||||
"2 / (x - 1) / 2",
|
||||
x -> 2 / (x - 1) / 2,
|
||||
new Divide(new Divide(c2, new Subtract(vx, c1)), c2)
|
||||
);
|
||||
}
|
||||
}
|
||||
30
java/expression/ExpressionTest.java
Normal file
30
java/expression/ExpressionTest.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package expression;
|
||||
|
||||
import base.Selector;
|
||||
import base.TestCounter;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class ExpressionTest {
|
||||
|
||||
public static final Selector SELECTOR = new Selector(
|
||||
ExpressionTest.class,
|
||||
"easy",
|
||||
"hard"
|
||||
).variant("Base", v(Expression::tester));
|
||||
|
||||
private ExpressionTest() {}
|
||||
|
||||
public static Consumer<TestCounter> v(
|
||||
final Function<TestCounter, ? extends ExpressionTester<?, ?>> tester
|
||||
) {
|
||||
return t -> tester.apply(t).test();
|
||||
}
|
||||
|
||||
public static void main(final String... args) {
|
||||
SELECTOR.main(args);
|
||||
}
|
||||
}
|
||||
415
java/expression/ExpressionTester.java
Normal file
415
java/expression/ExpressionTester.java
Normal file
@@ -0,0 +1,415 @@
|
||||
package expression;
|
||||
|
||||
import static base.Asserts.assertTrue;
|
||||
|
||||
import base.*;
|
||||
import expression.common.*;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
import java.util.function.BinaryOperator;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class ExpressionTester<E extends ToMiniString, C> extends Tester {
|
||||
|
||||
private final List<Integer> VALUES = IntStream.rangeClosed(-10, 10)
|
||||
.boxed()
|
||||
.toList();
|
||||
private final ExpressionKind<E, C> kind;
|
||||
|
||||
private final List<Test> basic = new ArrayList<>();
|
||||
private final List<Test> advanced = new ArrayList<>();
|
||||
private final Set<String> used = new HashSet<>();
|
||||
private final GeneratorBuilder generator;
|
||||
|
||||
private final List<Pair<ToMiniString, String>> prev = new ArrayList<>();
|
||||
private final Map<String, C> mappings;
|
||||
|
||||
protected ExpressionTester(
|
||||
final TestCounter counter,
|
||||
final ExpressionKind<E, C> kind,
|
||||
final Function<C, E> expectedConstant,
|
||||
final Binary<C, E> binary,
|
||||
final BinaryOperator<C> add,
|
||||
final BinaryOperator<C> sub,
|
||||
final BinaryOperator<C> mul,
|
||||
final BinaryOperator<C> div,
|
||||
final Map<String, C> mappings
|
||||
) {
|
||||
super(counter);
|
||||
this.kind = kind;
|
||||
this.mappings = mappings;
|
||||
|
||||
generator = new GeneratorBuilder(
|
||||
expectedConstant,
|
||||
kind::constant,
|
||||
binary,
|
||||
kind::randomValue
|
||||
);
|
||||
generator.binary("+", 1600, add, Add.class);
|
||||
generator.binary("-", 1602, sub, Subtract.class);
|
||||
generator.binary("*", 2001, mul, Multiply.class);
|
||||
generator.binary("/", 2002, div, Divide.class);
|
||||
}
|
||||
|
||||
protected ExpressionTester(
|
||||
final TestCounter counter,
|
||||
final ExpressionKind<E, C> kind,
|
||||
final Function<C, E> expectedConstant,
|
||||
final Binary<C, E> binary,
|
||||
final BinaryOperator<C> add,
|
||||
final BinaryOperator<C> sub,
|
||||
final BinaryOperator<C> mul,
|
||||
final BinaryOperator<C> div
|
||||
) {
|
||||
this(
|
||||
counter,
|
||||
kind,
|
||||
expectedConstant,
|
||||
binary,
|
||||
add,
|
||||
sub,
|
||||
mul,
|
||||
div,
|
||||
Map.of()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return kind.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void test() {
|
||||
counter.scope("Basic tests", () -> basic.forEach(Test::test));
|
||||
counter.scope("Advanced tests", () -> advanced.forEach(Test::test));
|
||||
counter.scope("Random tests", generator::testRandom);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "ConstantValue", "EqualsWithItself" })
|
||||
private void checkEqualsAndToString(
|
||||
final String full,
|
||||
final String mini,
|
||||
final ToMiniString expression,
|
||||
final ToMiniString copy
|
||||
) {
|
||||
checkToString("toString", full, expression.toString());
|
||||
if (mode() > 0) {
|
||||
checkToString("toMiniString", mini, expression.toMiniString());
|
||||
}
|
||||
|
||||
counter.test(() -> {
|
||||
assertTrue("Equals to this", expression.equals(expression));
|
||||
assertTrue("Equals to copy", expression.equals(copy));
|
||||
assertTrue("Equals to null", !expression.equals(null));
|
||||
assertTrue("Copy equals to null", !copy.equals(null));
|
||||
});
|
||||
|
||||
final String expressionToString = Objects.requireNonNull(
|
||||
expression.toString()
|
||||
);
|
||||
for (final Pair<ToMiniString, String> pair : prev) {
|
||||
counter.test(() -> {
|
||||
final ToMiniString prev = pair.first();
|
||||
final String prevToString = pair.second();
|
||||
final boolean equals = prevToString.equals(expressionToString);
|
||||
assertTrue(
|
||||
"Equals to " + prevToString,
|
||||
prev.equals(expression) == equals
|
||||
);
|
||||
assertTrue(
|
||||
"Equals to " + prevToString,
|
||||
expression.equals(prev) == equals
|
||||
);
|
||||
assertTrue(
|
||||
"Inconsistent hashCode for " + prev + " and " + expression,
|
||||
(prev.hashCode() == expression.hashCode()) == equals
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void checkToString(
|
||||
final String method,
|
||||
final String expected,
|
||||
final String actual
|
||||
) {
|
||||
counter.test(() ->
|
||||
assertTrue(
|
||||
String.format(
|
||||
"Invalid %s\n expected: %s\n actual: %s",
|
||||
method,
|
||||
expected,
|
||||
actual
|
||||
),
|
||||
expected.equals(actual)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private void check(
|
||||
final String full,
|
||||
final E expected,
|
||||
final E actual,
|
||||
final List<String> variables,
|
||||
final List<C> values
|
||||
) {
|
||||
final String vars = IntStream.range(0, variables.size())
|
||||
.mapToObj(i -> variables.get(i) + "=" + values.get(i))
|
||||
.collect(Collectors.joining(","));
|
||||
counter.test(() -> {
|
||||
final Object expectedResult = evaluate(expected, variables, values);
|
||||
final Object actualResult = evaluate(actual, variables, values);
|
||||
final String reason = String.format(
|
||||
"%s:%n expected `%s`,%n actual `%s`",
|
||||
String.format("f(%s)\nwhere f is %s", vars, full),
|
||||
Asserts.toString(expectedResult),
|
||||
Asserts.toString(actualResult)
|
||||
);
|
||||
if (
|
||||
expectedResult != null &&
|
||||
actualResult != null &&
|
||||
expectedResult.getClass() == actualResult.getClass() &&
|
||||
(expectedResult.getClass() == Double.class ||
|
||||
expectedResult.getClass() == Float.class)
|
||||
) {
|
||||
final double expectedValue = (
|
||||
(Number) expectedResult
|
||||
).doubleValue();
|
||||
final double actualValue = (
|
||||
(Number) actualResult
|
||||
).doubleValue();
|
||||
Asserts.assertEquals(reason, expectedValue, actualValue, 1e-6);
|
||||
} else {
|
||||
assertTrue(
|
||||
reason,
|
||||
Objects.deepEquals(expectedResult, actualResult)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Object evaluate(
|
||||
final E expression,
|
||||
final List<String> variables,
|
||||
final List<C> values
|
||||
) {
|
||||
try {
|
||||
return kind.evaluate(expression, variables, values);
|
||||
} catch (final Exception e) {
|
||||
return e.getClass().getName();
|
||||
}
|
||||
}
|
||||
|
||||
protected ExpressionTester<E, C> basic(
|
||||
final String full,
|
||||
final String mini,
|
||||
final E expected,
|
||||
final E actual
|
||||
) {
|
||||
return basicF(full, mini, expected, vars -> actual);
|
||||
}
|
||||
|
||||
protected ExpressionTester<E, C> basicF(
|
||||
final String full,
|
||||
final String mini,
|
||||
final E expected,
|
||||
final Function<List<String>, E> actual
|
||||
) {
|
||||
return basic(new Test(full, mini, expected, actual));
|
||||
}
|
||||
|
||||
private ExpressionTester<E, C> basic(final Test test) {
|
||||
Asserts.assertTrue(test.full, used.add(test.full));
|
||||
basic.add(test);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected ExpressionTester<E, C> advanced(
|
||||
final String full,
|
||||
final String mini,
|
||||
final E expected,
|
||||
final E actual
|
||||
) {
|
||||
return advancedF(full, mini, expected, vars -> actual);
|
||||
}
|
||||
|
||||
protected ExpressionTester<E, C> advancedF(
|
||||
final String full,
|
||||
final String mini,
|
||||
final E expected,
|
||||
final Function<List<String>, E> actual
|
||||
) {
|
||||
Asserts.assertTrue(full, used.add(full));
|
||||
advanced.add(new Test(full, mini, expected, actual));
|
||||
return this;
|
||||
}
|
||||
|
||||
protected static <E> Named<E> variable(
|
||||
final String name,
|
||||
final E expected
|
||||
) {
|
||||
return Named.of(name, expected);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Binary<C, E> {
|
||||
E apply(BinaryOperator<C> op, E a, E b);
|
||||
}
|
||||
|
||||
private final class Test {
|
||||
|
||||
private final String full;
|
||||
private final String mini;
|
||||
private final E expected;
|
||||
private final Function<List<String>, E> actual;
|
||||
|
||||
private Test(
|
||||
final String full,
|
||||
final String mini,
|
||||
final E expected,
|
||||
final Function<List<String>, E> actual
|
||||
) {
|
||||
this.full = full;
|
||||
this.mini = mini;
|
||||
this.expected = expected;
|
||||
this.actual = actual;
|
||||
}
|
||||
|
||||
private void test() {
|
||||
final List<Pair<String, E>> variables = kind
|
||||
.variables()
|
||||
.generate(random(), 3);
|
||||
final List<String> names = Functional.map(variables, Pair::first);
|
||||
final E actual = kind.cast(this.actual.apply(names));
|
||||
final String full = mangle(this.full, names);
|
||||
final String mini = mangle(this.mini, names);
|
||||
|
||||
counter.test(() -> {
|
||||
kind
|
||||
.allValues(variables.size(), VALUES)
|
||||
.forEach(values ->
|
||||
check(mini, expected, actual, names, values)
|
||||
);
|
||||
checkEqualsAndToString(full, mini, actual, actual);
|
||||
prev.add(Pair.of(actual, full));
|
||||
});
|
||||
}
|
||||
|
||||
private String mangle(String string, final List<String> names) {
|
||||
for (int i = 0; i < names.size(); i++) {
|
||||
string = string.replace("$" + (char) ('x' + i), names.get(i));
|
||||
}
|
||||
for (final Map.Entry<String, C> mapping : mappings.entrySet()) {
|
||||
string = string.replace(
|
||||
mapping.getKey(),
|
||||
mapping.getValue().toString()
|
||||
);
|
||||
}
|
||||
return string;
|
||||
}
|
||||
}
|
||||
|
||||
private final class GeneratorBuilder {
|
||||
|
||||
private final Generator.Builder<C> generator;
|
||||
private final NodeRendererBuilder<C> renderer =
|
||||
new NodeRendererBuilder<>(random());
|
||||
private final Renderer.Builder<C, Unit, E> expected;
|
||||
private final Renderer.Builder<C, Unit, E> actual;
|
||||
private final Renderer.Builder<C, Unit, E> copy;
|
||||
private final Binary<C, E> binary;
|
||||
|
||||
private GeneratorBuilder(
|
||||
final Function<C, E> expectedConstant,
|
||||
final Function<? super C, E> actualConstant,
|
||||
final Binary<C, E> binary,
|
||||
final Function<ExtendedRandom, C> randomValue
|
||||
) {
|
||||
generator = Generator.builder(
|
||||
() -> randomValue.apply(random()),
|
||||
random()
|
||||
);
|
||||
expected = Renderer.builder(expectedConstant::apply);
|
||||
actual = Renderer.builder(actualConstant::apply);
|
||||
copy = Renderer.builder(actualConstant::apply);
|
||||
|
||||
this.binary = binary;
|
||||
}
|
||||
|
||||
private void binary(
|
||||
final String name,
|
||||
final int priority,
|
||||
final BinaryOperator<C> op,
|
||||
final Class<?> type
|
||||
) {
|
||||
generator.add(name, 2);
|
||||
renderer.binary(name, priority);
|
||||
|
||||
expected.binary(name, (unit, a, b) -> binary.apply(op, a, b));
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
final Constructor<? extends E> constructor = (Constructor<
|
||||
? extends E
|
||||
>) Arrays.stream(type.getConstructors())
|
||||
.filter(cons -> Modifier.isPublic(cons.getModifiers()))
|
||||
.filter(cons -> cons.getParameterCount() == 2)
|
||||
.findFirst()
|
||||
.orElseGet(() ->
|
||||
counter.fail(
|
||||
"%s(..., ...) constructor not found",
|
||||
type.getSimpleName()
|
||||
)
|
||||
);
|
||||
final Renderer.BinaryOperator<Unit, E> actual = (unit, a, b) -> {
|
||||
try {
|
||||
return constructor.newInstance(a, b);
|
||||
} catch (final Exception e) {
|
||||
return counter.fail(e);
|
||||
}
|
||||
};
|
||||
this.actual.binary(name, actual);
|
||||
copy.binary(name, actual);
|
||||
}
|
||||
|
||||
private void testRandom() {
|
||||
final NodeRenderer<C> renderer = this.renderer.build();
|
||||
final Renderer<C, Unit, E> expectedRenderer = this.expected.build();
|
||||
final Renderer<C, Unit, E> actualRenderer = this.actual.build();
|
||||
final expression.common.Generator<C, E> generator =
|
||||
this.generator.build(kind.variables(), List.of());
|
||||
generator.testRandom(counter, 1, expr -> {
|
||||
final String full = renderer.render(expr, NodeRenderer.FULL);
|
||||
final String mini = renderer.render(expr, NodeRenderer.MINI);
|
||||
final E expected = expectedRenderer.render(expr, Unit.INSTANCE);
|
||||
final E actual = actualRenderer.render(expr, Unit.INSTANCE);
|
||||
|
||||
final List<Pair<String, E>> variables = expr.variables();
|
||||
final List<String> names = Functional.map(
|
||||
variables,
|
||||
Pair::first
|
||||
);
|
||||
final List<C> values = Stream.generate(() ->
|
||||
kind.randomValue(random())
|
||||
)
|
||||
.limit(variables.size())
|
||||
.toList();
|
||||
|
||||
checkEqualsAndToString(
|
||||
full,
|
||||
mini,
|
||||
actual,
|
||||
copy.build().render(expr, Unit.INSTANCE)
|
||||
);
|
||||
check(full, expected, actual, names, values);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
75
java/expression/Floor.java
Normal file
75
java/expression/Floor.java
Normal file
@@ -0,0 +1,75 @@
|
||||
package expression;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class Floor extends AbstractExpression {
|
||||
|
||||
private final AbstractExpression operand;
|
||||
|
||||
public Floor(AbstractExpression operand) {
|
||||
this.operand = operand;
|
||||
}
|
||||
|
||||
private static int floorInt(int n) {
|
||||
return Math.floorDiv(n, 1000) * 1000;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int evaluate(int x) {
|
||||
return floorInt(operand.evaluate(x));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int evaluate(int x, int y, int z) {
|
||||
return floorInt(operand.evaluate(x, y, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int evaluate(List<Integer> vars) {
|
||||
return floorInt(operand.evaluate(vars));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger evaluateBi(List<BigInteger> vars) {
|
||||
throw new UnsupportedOperationException(
|
||||
"floor not supported for BigInteger"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal evaluateBd(List<BigDecimal> vars) {
|
||||
throw new UnsupportedOperationException(
|
||||
"floor not supported for BigDecimal"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "floor(" + operand + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toMiniString() {
|
||||
if (operand instanceof AbstractBinaryOperation) {
|
||||
return "floor(" + operand.toMiniString() + ")";
|
||||
}
|
||||
return "floor " + operand.toMiniString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Floor)) return false;
|
||||
return operand.equals(((Floor) obj).operand);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return operand.hashCode() ^ 0x464C4F52;
|
||||
}
|
||||
}
|
||||
51
java/expression/High.java
Normal file
51
java/expression/High.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package expression;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class High extends AbstractExpression {
|
||||
private final AbstractExpression operand;
|
||||
|
||||
public High(AbstractExpression operand) {
|
||||
this.operand = operand;
|
||||
}
|
||||
|
||||
private static int highestBit(int n) {
|
||||
if (n == 0) return 0;
|
||||
n |= (n >> 1);
|
||||
n |= (n >> 2);
|
||||
n |= (n >> 4);
|
||||
n |= (n >> 8);
|
||||
n |= (n >> 16);
|
||||
return n - (n >>> 1);
|
||||
}
|
||||
|
||||
@Override public int evaluate(int x) { return highestBit(operand.evaluate(x)); }
|
||||
@Override public int evaluate(int x, int y, int z) { return highestBit(operand.evaluate(x,y,z)); }
|
||||
@Override public int evaluate(List<Integer> vars) { return highestBit(operand.evaluate(vars)); }
|
||||
@Override public BigInteger evaluateBi(List<BigInteger> vars) { throw new UnsupportedOperationException(); }
|
||||
@Override public BigDecimal evaluateBd(List<BigDecimal> vars) { throw new UnsupportedOperationException(); }
|
||||
|
||||
@Override public String toString() { return "high(" + operand + ")"; }
|
||||
|
||||
@Override
|
||||
public String toMiniString() {
|
||||
if (operand instanceof AbstractBinaryOperation) {
|
||||
return "high(" + operand.toMiniString() + ")";
|
||||
}
|
||||
return "high " + operand.toMiniString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof High)) return false;
|
||||
return operand.equals(((High) obj).operand);
|
||||
}
|
||||
|
||||
@Override public int hashCode() { return operand.hashCode() ^ 0x48494748; }
|
||||
}
|
||||
26
java/expression/ListExpression.java
Normal file
26
java/expression/ListExpression.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package expression;
|
||||
|
||||
import base.ExtendedRandom;
|
||||
import base.Pair;
|
||||
import expression.common.ExpressionKind;
|
||||
import expression.common.Type;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ListExpression extends ToMiniString {
|
||||
ExpressionKind<ListExpression, Integer> KIND = new ExpressionKind<>(
|
||||
new Type<>(a -> a, ExtendedRandom::nextInt, int.class),
|
||||
ListExpression.class,
|
||||
(r, c) -> IntStream.range(0, c)
|
||||
.mapToObj(name -> Pair.<String, ListExpression>of("$" + name, new Variable(name)))
|
||||
.toList(),
|
||||
(expr, variables, values) -> expr.evaluate(values)
|
||||
);
|
||||
|
||||
int evaluate(List<Integer> variables);
|
||||
}
|
||||
46
java/expression/Log.java
Normal file
46
java/expression/Log.java
Normal file
@@ -0,0 +1,46 @@
|
||||
package expression;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class Log extends AbstractBinaryOperation {
|
||||
public Log(AbstractExpression l, AbstractExpression r) { super(l, r); }
|
||||
|
||||
@Override protected String getOperator() { return "//"; }
|
||||
@Override protected int getPriority() { return 3; }
|
||||
@Override protected boolean isRightAssoc() { return true; }
|
||||
|
||||
@Override
|
||||
protected int applyInt(int a, int b) {
|
||||
if (a <= 0) throw new ArithmeticException("logarithm of non-positive number");
|
||||
if (b <= 1) throw new ArithmeticException("logarithm base must be > 1");
|
||||
int result = 0;
|
||||
int power = 1;
|
||||
while (power <= a / b) {
|
||||
power *= b;
|
||||
result++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BigInteger applyBi(BigInteger a, BigInteger b) {
|
||||
if (a.signum() <= 0) throw new ArithmeticException("logarithm of non-positive number");
|
||||
if (b.compareTo(BigInteger.TWO) < 0) throw new ArithmeticException("logarithm base must be > 1");
|
||||
int result = 0;
|
||||
BigInteger power = BigInteger.ONE;
|
||||
while (power.multiply(b).compareTo(a) <= 0) {
|
||||
power = power.multiply(b);
|
||||
result++;
|
||||
}
|
||||
return BigInteger.valueOf(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BigDecimal applyBd(BigDecimal a, BigDecimal b) {
|
||||
throw new UnsupportedOperationException("log not supported for BigDecimal");
|
||||
}
|
||||
}
|
||||
52
java/expression/Log2.java
Normal file
52
java/expression/Log2.java
Normal file
@@ -0,0 +1,52 @@
|
||||
package expression;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class Log2 extends AbstractExpression {
|
||||
private final AbstractExpression operand;
|
||||
|
||||
public Log2(AbstractExpression operand) {
|
||||
this.operand = operand;
|
||||
}
|
||||
|
||||
private static int log2(int n) {
|
||||
if (n <= 0) throw new ArithmeticException("log₂ of non-positive number: " + n);
|
||||
int result = 0;
|
||||
int v = n;
|
||||
while (v > 1) {
|
||||
v >>= 1;
|
||||
result++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override public int evaluate(int x) { return log2(operand.evaluate(x)); }
|
||||
@Override public int evaluate(int x, int y, int z) { return log2(operand.evaluate(x,y,z)); }
|
||||
@Override public int evaluate(List<Integer> vars) { return log2(operand.evaluate(vars)); }
|
||||
@Override public BigInteger evaluateBi(List<BigInteger> vars) { throw new UnsupportedOperationException(); }
|
||||
@Override public BigDecimal evaluateBd(List<BigDecimal> vars) { throw new UnsupportedOperationException(); }
|
||||
|
||||
@Override public String toString() { return "log₂(" + operand + ")"; }
|
||||
|
||||
@Override
|
||||
public String toMiniString() {
|
||||
if (operand instanceof AbstractBinaryOperation) {
|
||||
return "log₂(" + operand.toMiniString() + ")";
|
||||
}
|
||||
return "log₂ " + operand.toMiniString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Log2)) return false;
|
||||
return operand.equals(((Log2) obj).operand);
|
||||
}
|
||||
|
||||
@Override public int hashCode() { return operand.hashCode() ^ 0x4C4F4732; }
|
||||
}
|
||||
41
java/expression/Low.java
Normal file
41
java/expression/Low.java
Normal file
@@ -0,0 +1,41 @@
|
||||
package expression;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class Low extends AbstractExpression {
|
||||
private final AbstractExpression operand;
|
||||
|
||||
public Low(AbstractExpression operand) {
|
||||
this.operand = operand;
|
||||
}
|
||||
|
||||
@Override public int evaluate(int x) { int n = operand.evaluate(x); return n & -n; }
|
||||
@Override public int evaluate(int x, int y, int z) { int n = operand.evaluate(x,y,z); return n & -n; }
|
||||
@Override public int evaluate(List<Integer> vars) { int n = operand.evaluate(vars); return n & -n; }
|
||||
@Override public BigInteger evaluateBi(List<BigInteger> vars) { throw new UnsupportedOperationException(); }
|
||||
@Override public BigDecimal evaluateBd(List<BigDecimal> vars) { throw new UnsupportedOperationException(); }
|
||||
|
||||
@Override public String toString() { return "low(" + operand + ")"; }
|
||||
|
||||
@Override
|
||||
public String toMiniString() {
|
||||
if (operand instanceof AbstractBinaryOperation) {
|
||||
return "low(" + operand.toMiniString() + ")";
|
||||
}
|
||||
return "low " + operand.toMiniString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Low)) return false;
|
||||
return operand.equals(((Low) obj).operand);
|
||||
}
|
||||
|
||||
@Override public int hashCode() { return operand.hashCode() ^ 0x4C4F5700; }
|
||||
}
|
||||
44
java/expression/Max.java
Normal file
44
java/expression/Max.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package expression;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class Max extends AbstractBinaryOperation {
|
||||
|
||||
public Max(AbstractExpression l, AbstractExpression r) {
|
||||
super(l, r);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getOperator() {
|
||||
return "max";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPriority() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isRightAssoc() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int applyInt(int a, int b) {
|
||||
return Math.max(a, b);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BigInteger applyBi(BigInteger a, BigInteger b) {
|
||||
return a.max(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BigDecimal applyBd(BigDecimal a, BigDecimal b) {
|
||||
return a.max(b);
|
||||
}
|
||||
}
|
||||
44
java/expression/Min.java
Normal file
44
java/expression/Min.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package expression;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class Min extends AbstractBinaryOperation {
|
||||
|
||||
public Min(AbstractExpression l, AbstractExpression r) {
|
||||
super(l, r);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getOperator() {
|
||||
return "min";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPriority() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isRightAssoc() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int applyInt(int a, int b) {
|
||||
return Math.min(a, b);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BigInteger applyBi(BigInteger a, BigInteger b) {
|
||||
return a.min(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BigDecimal applyBd(BigDecimal a, BigDecimal b) {
|
||||
return a.min(b);
|
||||
}
|
||||
}
|
||||
44
java/expression/Multiply.java
Normal file
44
java/expression/Multiply.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package expression;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class Multiply extends AbstractBinaryOperation {
|
||||
|
||||
public Multiply(AbstractExpression l, AbstractExpression r) {
|
||||
super(l, r);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getOperator() {
|
||||
return "*";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPriority() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isRightAssoc() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int applyInt(int a, int b) {
|
||||
return a * b;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BigInteger applyBi(BigInteger a, BigInteger b) {
|
||||
return a.multiply(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BigDecimal applyBd(BigDecimal a, BigDecimal b) {
|
||||
return a.multiply(b);
|
||||
}
|
||||
}
|
||||
10
java/expression/OverflowException.java
Normal file
10
java/expression/OverflowException.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package expression;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class OverflowException extends ArithmeticException {
|
||||
public OverflowException(String operation) {
|
||||
super("overflow in " + operation);
|
||||
}
|
||||
}
|
||||
47
java/expression/Pow2.java
Normal file
47
java/expression/Pow2.java
Normal file
@@ -0,0 +1,47 @@
|
||||
package expression;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class Pow2 extends AbstractExpression {
|
||||
private final AbstractExpression operand;
|
||||
|
||||
public Pow2(AbstractExpression operand) {
|
||||
this.operand = operand;
|
||||
}
|
||||
|
||||
private static int pow2(int n) {
|
||||
if (n < 0) throw new ArithmeticException("pow₂ of negative number: " + n);
|
||||
if (n >= 31) throw new OverflowException("pow₂");
|
||||
return 1 << n;
|
||||
}
|
||||
|
||||
@Override public int evaluate(int x) { return pow2(operand.evaluate(x)); }
|
||||
@Override public int evaluate(int x, int y, int z) { return pow2(operand.evaluate(x,y,z)); }
|
||||
@Override public int evaluate(List<Integer> vars) { return pow2(operand.evaluate(vars)); }
|
||||
@Override public BigInteger evaluateBi(List<BigInteger> vars) { throw new UnsupportedOperationException(); }
|
||||
@Override public BigDecimal evaluateBd(List<BigDecimal> vars) { throw new UnsupportedOperationException(); }
|
||||
|
||||
@Override public String toString() { return "pow₂(" + operand + ")"; }
|
||||
|
||||
@Override
|
||||
public String toMiniString() {
|
||||
if (operand instanceof AbstractBinaryOperation) {
|
||||
return "pow₂(" + operand.toMiniString() + ")";
|
||||
}
|
||||
return "pow₂ " + operand.toMiniString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Pow2)) return false;
|
||||
return operand.equals(((Pow2) obj).operand);
|
||||
}
|
||||
|
||||
@Override public int hashCode() { return operand.hashCode() ^ 0x504F5732; }
|
||||
}
|
||||
46
java/expression/Power.java
Normal file
46
java/expression/Power.java
Normal file
@@ -0,0 +1,46 @@
|
||||
package expression;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class Power extends AbstractBinaryOperation {
|
||||
public Power(AbstractExpression l, AbstractExpression r) { super(l, r); }
|
||||
|
||||
@Override protected String getOperator() { return "**"; }
|
||||
@Override protected int getPriority() { return 3; }
|
||||
@Override protected boolean isRightAssoc() { return true; }
|
||||
|
||||
@Override
|
||||
protected int applyInt(int base, int exp) {
|
||||
if (exp < 0) throw new ArithmeticException("negative exponent");
|
||||
if (base == 0 && exp == 0) throw new ArithmeticException("zero to the power of zero");
|
||||
if (exp == 0) return 1;
|
||||
if (base == 0) return 0;
|
||||
int result = 1;
|
||||
int b = base;
|
||||
int e = exp;
|
||||
while (e > 0) {
|
||||
if ((e & 1) == 1) {
|
||||
result = checkedMul(result, b);
|
||||
}
|
||||
if (e > 1) b = checkedMul(b, b);
|
||||
e >>= 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int checkedMul(int a, int b) {
|
||||
if (a == 0 || b == 0) return 0;
|
||||
if (a == Integer.MIN_VALUE && b == -1) throw new OverflowException("power");
|
||||
if (b == Integer.MIN_VALUE && a == -1) throw new OverflowException("power");
|
||||
int result = a * b;
|
||||
if (result / a != b) throw new OverflowException("power");
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override protected BigInteger applyBi(BigInteger a, BigInteger b) { return a.pow(b.intValueExact()); }
|
||||
@Override protected BigDecimal applyBd(BigDecimal a, BigDecimal b) { return a.pow(b.intValueExact()); }
|
||||
}
|
||||
84
java/expression/Reverse.java
Normal file
84
java/expression/Reverse.java
Normal file
@@ -0,0 +1,84 @@
|
||||
package expression;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class Reverse extends AbstractExpression {
|
||||
|
||||
private final AbstractExpression operand;
|
||||
|
||||
public Reverse(AbstractExpression operand) {
|
||||
this.operand = operand;
|
||||
}
|
||||
|
||||
private static int reverseInt(int n) {
|
||||
boolean negative = n < 0;
|
||||
long abs = Math.abs((long) n);
|
||||
long reversed = 0;
|
||||
while (abs > 0) {
|
||||
reversed = reversed * 10 + (abs % 10);
|
||||
abs /= 10;
|
||||
}
|
||||
long result = negative ? -reversed : reversed;
|
||||
return (int) result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int evaluate(int x) {
|
||||
return reverseInt(operand.evaluate(x));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int evaluate(int x, int y, int z) {
|
||||
return reverseInt(operand.evaluate(x, y, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int evaluate(List<Integer> vars) {
|
||||
return reverseInt(operand.evaluate(vars));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger evaluateBi(List<BigInteger> vars) {
|
||||
throw new UnsupportedOperationException(
|
||||
"reverse not supported for BigInteger"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal evaluateBd(List<BigDecimal> vars) {
|
||||
throw new UnsupportedOperationException(
|
||||
"reverse not supported for BigDecimal"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "reverse(" + operand + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toMiniString() {
|
||||
if (operand instanceof AbstractBinaryOperation) {
|
||||
return "reverse(" + operand.toMiniString() + ")";
|
||||
}
|
||||
|
||||
return "reverse " + operand.toMiniString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Reverse)) return false;
|
||||
return operand.equals(((Reverse) obj).operand);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return operand.hashCode() ^ 0x52455645;
|
||||
}
|
||||
}
|
||||
46
java/expression/SetBit.java
Normal file
46
java/expression/SetBit.java
Normal file
@@ -0,0 +1,46 @@
|
||||
package expression;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class SetBit extends AbstractBinaryOperation {
|
||||
|
||||
public SetBit(AbstractExpression l, AbstractExpression r) {
|
||||
super(l, r);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getOperator() {
|
||||
return "set";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPriority() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isRightAssoc() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int applyInt(int a, int b) {
|
||||
return a | (1 << b);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BigInteger applyBi(BigInteger a, BigInteger b) {
|
||||
return a.setBit(b.intValueExact());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BigDecimal applyBd(BigDecimal a, BigDecimal b) {
|
||||
throw new UnsupportedOperationException(
|
||||
"set not supported for BigDecimal"
|
||||
);
|
||||
}
|
||||
}
|
||||
60
java/expression/Sqrt.java
Normal file
60
java/expression/Sqrt.java
Normal file
@@ -0,0 +1,60 @@
|
||||
package expression;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class Sqrt extends AbstractExpression {
|
||||
private final AbstractExpression operand;
|
||||
|
||||
public Sqrt(AbstractExpression operand) {
|
||||
this.operand = operand;
|
||||
}
|
||||
|
||||
private static int sqrtInt(int n) {
|
||||
if (n < 0) throw new ArithmeticException("sqrt of negative number: " + n);
|
||||
if (n == 0) return 0;
|
||||
// Binary search: floor(sqrt(n)), result <= 46340 (sqrt(MAX_INT) < 46341)
|
||||
int lo = 0, hi = 46340;
|
||||
while (lo < hi) {
|
||||
int mid = (lo + hi + 1) / 2;
|
||||
if (mid * mid <= n) {
|
||||
lo = mid;
|
||||
} else {
|
||||
hi = mid - 1;
|
||||
}
|
||||
}
|
||||
return lo;
|
||||
}
|
||||
|
||||
@Override public int evaluate(int x) { return sqrtInt(operand.evaluate(x)); }
|
||||
@Override public int evaluate(int x, int y, int z) { return sqrtInt(operand.evaluate(x,y,z)); }
|
||||
@Override public int evaluate(List<Integer> vars) { return sqrtInt(operand.evaluate(vars)); }
|
||||
@Override public BigInteger evaluateBi(List<BigInteger> vars) { return operand.evaluateBi(vars).sqrt(); }
|
||||
@Override public BigDecimal evaluateBd(List<BigDecimal> vars) {
|
||||
throw new UnsupportedOperationException("sqrt not supported for BigDecimal");
|
||||
}
|
||||
|
||||
@Override public String toString() { return "√(" + operand + ")"; }
|
||||
// @Override public String toMiniString() { return "√" + operand.toMiniString(); }
|
||||
|
||||
@Override
|
||||
public String toMiniString() {
|
||||
if (operand instanceof AbstractBinaryOperation) {
|
||||
return "√(" + operand.toMiniString() + ")";
|
||||
}
|
||||
return "√" + operand.toMiniString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Sqrt)) return false;
|
||||
return operand.equals(((Sqrt) obj).operand);
|
||||
}
|
||||
|
||||
@Override public int hashCode() { return operand.hashCode() ^ 0x53515254; }
|
||||
}
|
||||
51
java/expression/Square.java
Normal file
51
java/expression/Square.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package expression;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class Square extends AbstractExpression {
|
||||
private final AbstractExpression operand;
|
||||
|
||||
public Square(AbstractExpression operand) {
|
||||
this.operand = operand;
|
||||
}
|
||||
|
||||
private static int squareInt(int n) {
|
||||
if (n == Integer.MIN_VALUE) throw new OverflowException("square");
|
||||
int result = n * n;
|
||||
// overflow check: result / n should == n (if n != 0)
|
||||
if (n != 0 && result / n != n) throw new OverflowException("square");
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override public int evaluate(int x) { return squareInt(operand.evaluate(x)); }
|
||||
@Override public int evaluate(int x, int y, int z) { return squareInt(operand.evaluate(x,y,z)); }
|
||||
@Override public int evaluate(List<Integer> vars) { return squareInt(operand.evaluate(vars)); }
|
||||
@Override public BigInteger evaluateBi(List<BigInteger> vars) { return operand.evaluateBi(vars).pow(2); }
|
||||
@Override public BigDecimal evaluateBd(List<BigDecimal> vars) { return operand.evaluateBd(vars).pow(2); }
|
||||
|
||||
@Override public String toString() { return "(" + operand + ")²"; }
|
||||
|
||||
@Override
|
||||
public String toMiniString() {
|
||||
String s = operand.toMiniString();
|
||||
boolean needParens = operand instanceof AbstractBinaryOperation
|
||||
|| operand instanceof Cbrt
|
||||
|| operand instanceof Sqrt
|
||||
|| (s.startsWith("-") && (s.length() < 2 || !Character.isDigit(s.charAt(1))));
|
||||
return needParens ? "(" + s + ")²" : s + "²";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Square)) return false;
|
||||
return operand.equals(((Square) obj).operand);
|
||||
}
|
||||
|
||||
@Override public int hashCode() { return operand.hashCode() ^ 0x53515232; }
|
||||
}
|
||||
44
java/expression/Subtract.java
Normal file
44
java/expression/Subtract.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package expression;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class Subtract extends AbstractBinaryOperation {
|
||||
|
||||
public Subtract(AbstractExpression l, AbstractExpression r) {
|
||||
super(l, r);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getOperator() {
|
||||
return "-";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPriority() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isRightAssoc() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int applyInt(int a, int b) {
|
||||
return a - b;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BigInteger applyBi(BigInteger a, BigInteger b) {
|
||||
return a.subtract(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BigDecimal applyBd(BigDecimal a, BigDecimal b) {
|
||||
return a.subtract(b);
|
||||
}
|
||||
}
|
||||
10
java/expression/ToMiniString.java
Normal file
10
java/expression/ToMiniString.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package expression;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public interface ToMiniString {
|
||||
default String toMiniString() {
|
||||
return toString();
|
||||
}
|
||||
}
|
||||
317
java/expression/TripleExpression.java
Normal file
317
java/expression/TripleExpression.java
Normal file
@@ -0,0 +1,317 @@
|
||||
package expression;
|
||||
|
||||
import base.ExtendedRandom;
|
||||
import base.Pair;
|
||||
import base.Selector;
|
||||
import base.TestCounter;
|
||||
import expression.common.ExpressionKind;
|
||||
import expression.common.Type;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Three-argument arithmetic expression over integers.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@SuppressWarnings("ClassReferencesSubclass")
|
||||
public interface TripleExpression extends ToMiniString {
|
||||
int evaluate(int x, int y, int z);
|
||||
|
||||
// Tests follow. You may temporarily remove everything til the end.
|
||||
|
||||
Type<Integer> TYPE = new Type<>(a -> a, ExtendedRandom::nextInt, int.class);
|
||||
ExpressionKind<TripleExpression, Integer> KIND = new ExpressionKind<>(
|
||||
TYPE,
|
||||
TripleExpression.class,
|
||||
List.of(
|
||||
Pair.of("x", new Variable("x")),
|
||||
Pair.of("y", new Variable("y")),
|
||||
Pair.of("z", new Variable("z"))
|
||||
),
|
||||
(expr, variables, values) ->
|
||||
expr.evaluate(values.get(0), values.get(1), values.get(2))
|
||||
);
|
||||
|
||||
@SuppressWarnings("PointlessArithmeticExpression")
|
||||
static ExpressionTester<?, ?> tester(final TestCounter counter) {
|
||||
final Variable vx = new Variable("x");
|
||||
final Variable vy = new Variable("y");
|
||||
final Variable vz = new Variable("z");
|
||||
|
||||
return new ExpressionTester<>(
|
||||
counter,
|
||||
KIND,
|
||||
c -> (x, y, z) -> c,
|
||||
(op, a, b) ->
|
||||
(x, y, z) -> op.apply(a.evaluate(x, y, z), b.evaluate(x, y, z)),
|
||||
Integer::sum,
|
||||
(a, b) -> a - b,
|
||||
(a, b) -> a * b,
|
||||
(a, b) -> a / b
|
||||
)
|
||||
.basic("10", "10", (x, y, z) -> 10, c(10))
|
||||
.basic("x", "x", (x, y, z) -> x, vx)
|
||||
.basic("y", "y", (x, y, z) -> y, vy)
|
||||
.basic("z", "z", (x, y, z) -> z, vz)
|
||||
.basic("(x + 2)", "x + 2", (x, y, z) -> x + 2, new Add(vx, c(2)))
|
||||
.basic(
|
||||
"(2 - y)",
|
||||
"2 - y",
|
||||
(x, y, z) -> 2 - y,
|
||||
new Subtract(c(2), vy)
|
||||
)
|
||||
.basic(
|
||||
"(3 * z)",
|
||||
"3 * z",
|
||||
(x, y, z) -> 3 * z,
|
||||
new Multiply(c(3), vz)
|
||||
)
|
||||
.basic(
|
||||
"(x / -2)",
|
||||
"x / -2",
|
||||
(x, y, z) -> -x / 2,
|
||||
new Divide(vx, c(-2))
|
||||
)
|
||||
.basic(
|
||||
"((1 + 2) + 3)",
|
||||
"1 + 2 + 3",
|
||||
(x, y, z) -> 6,
|
||||
new Add(new Add(c(1), c(2)), c(3))
|
||||
)
|
||||
.basic(
|
||||
"(1 + (2 + 3))",
|
||||
"1 + 2 + 3",
|
||||
(x, y, z) -> 6,
|
||||
new Add(c(1), new Add(c(2), c(3)))
|
||||
)
|
||||
.basic(
|
||||
"((1 - 2) - 3)",
|
||||
"1 - 2 - 3",
|
||||
(x, y, z) -> -4,
|
||||
new Subtract(new Subtract(c(1), c(2)), c(3))
|
||||
)
|
||||
.basic(
|
||||
"(1 - (2 - 3))",
|
||||
"1 - (2 - 3)",
|
||||
(x, y, z) -> 2,
|
||||
new Subtract(c(1), new Subtract(c(2), c(3)))
|
||||
)
|
||||
.basic(
|
||||
"((1 * 2) * 3)",
|
||||
"1 * 2 * 3",
|
||||
(x, y, z) -> 6,
|
||||
new Multiply(new Multiply(c(1), c(2)), c(3))
|
||||
)
|
||||
.basic(
|
||||
"(1 * (2 * 3))",
|
||||
"1 * 2 * 3",
|
||||
(x, y, z) -> 6,
|
||||
new Multiply(c(1), new Multiply(c(2), c(3)))
|
||||
)
|
||||
.basic(
|
||||
"((10 / 2) / 3)",
|
||||
"10 / 2 / 3",
|
||||
(x, y, z) -> 10 / 2 / 3,
|
||||
new Divide(new Divide(c(10), c(2)), c(3))
|
||||
)
|
||||
.basic(
|
||||
"(10 / (3 / 2))",
|
||||
"10 / (3 / 2)",
|
||||
(x, y, z) -> 10,
|
||||
new Divide(c(10), new Divide(c(3), c(2)))
|
||||
)
|
||||
.basic(
|
||||
"((x * y) + ((z - 1) / 10))",
|
||||
"x * y + (z - 1) / 10",
|
||||
(x, y, z) -> x * y + (z - 1) / 10,
|
||||
new Add(
|
||||
new Multiply(vx, vy),
|
||||
new Divide(new Subtract(vz, c(1)), c(10))
|
||||
)
|
||||
)
|
||||
.basic("(x + y)", "x + y", (x, y, z) -> x + y, new Add(vx, vy))
|
||||
.basic("(y + x)", "y + x", (x, y, z) -> y + x, new Add(vy, vx))
|
||||
.advanced(
|
||||
"(1 + 1)",
|
||||
"1 + 1",
|
||||
(x, y, z) -> 1 + 1,
|
||||
new Add(c(1), c(1))
|
||||
)
|
||||
.advanced(
|
||||
"(y - x)",
|
||||
"y - x",
|
||||
(x, y, z) -> y - x,
|
||||
new Subtract(vy, vx)
|
||||
)
|
||||
.advanced(
|
||||
"(2 * x)",
|
||||
"2 * x",
|
||||
(x, y, z) -> 2 * x,
|
||||
new Multiply(c(2), vx)
|
||||
)
|
||||
.advanced(
|
||||
"(2 / x)",
|
||||
"2 / x",
|
||||
(x, y, z) -> 2 / x,
|
||||
new Divide(c(2), vx)
|
||||
)
|
||||
.advanced(
|
||||
"(z + (1 + 1))",
|
||||
"z + 1 + 1",
|
||||
(x, y, z) -> z + 1 + 1,
|
||||
new Add(vz, new Add(c(1), c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"(2 - (y - x))",
|
||||
"2 - (y - x)",
|
||||
(x, y, z) -> 2 - (y - x),
|
||||
new Subtract(c(2), new Subtract(vy, vx))
|
||||
)
|
||||
.advanced(
|
||||
"(z * (2 / x))",
|
||||
"z * (2 / x)",
|
||||
(x, y, z) -> z * (2 / x),
|
||||
new Multiply(vz, new Divide(c(2), vx))
|
||||
)
|
||||
.advanced(
|
||||
"(z / (y - x))",
|
||||
"z / (y - x)",
|
||||
(x, y, z) -> z / (y - x),
|
||||
new Divide(vz, new Subtract(vy, vx))
|
||||
)
|
||||
.advanced(
|
||||
"((2 * x) + y)",
|
||||
"2 * x + y",
|
||||
(x, y, z) -> 2 * x + y,
|
||||
new Add(new Multiply(c(2), vx), vy)
|
||||
)
|
||||
.advanced(
|
||||
"((y - x) - 2)",
|
||||
"y - x - 2",
|
||||
(x, y, z) -> y - x - 2,
|
||||
new Subtract(new Subtract(vy, vx), c(2))
|
||||
)
|
||||
.advanced(
|
||||
"((2 / x) * y)",
|
||||
"2 / x * y",
|
||||
(x, y, z) -> (2 / x) * y,
|
||||
new Multiply(new Divide(c(2), vx), vy)
|
||||
)
|
||||
.advanced(
|
||||
"((1 + 1) / x)",
|
||||
"(1 + 1) / x",
|
||||
(x, y, z) -> (1 + 1) / x,
|
||||
new Divide(new Add(c(1), c(1)), vx)
|
||||
)
|
||||
.advanced(
|
||||
"(1 + (2 * 3))",
|
||||
"1 + 2 * 3",
|
||||
(x, y, z) -> 7,
|
||||
new Add(c(1), new Multiply(c(2), c(3)))
|
||||
)
|
||||
.advanced(
|
||||
"(1 - (2 * 3))",
|
||||
"1 - 2 * 3",
|
||||
(x, y, z) -> -5,
|
||||
new Subtract(c(1), new Multiply(c(2), c(3)))
|
||||
)
|
||||
.advanced(
|
||||
"(1 + (2 / 3))",
|
||||
"1 + 2 / 3",
|
||||
(x, y, z) -> 1,
|
||||
new Add(c(1), new Divide(c(2), c(3)))
|
||||
)
|
||||
.advanced(
|
||||
"(1 - (2 / 3))",
|
||||
"1 - 2 / 3",
|
||||
(x, y, z) -> 1,
|
||||
new Subtract(c(1), new Divide(c(2), c(3)))
|
||||
)
|
||||
.advanced(
|
||||
"(2 + (z + (1 + 1)))",
|
||||
"2 + z + 1 + 1",
|
||||
(x, y, z) -> 2 + z + 1 + 1,
|
||||
new Add(c(2), new Add(vz, new Add(c(1), c(1))))
|
||||
)
|
||||
.advanced(
|
||||
"(1 - ((2 * x) + y))",
|
||||
"1 - (2 * x + y)",
|
||||
(x, y, z) -> 1 - (2 * x + y),
|
||||
new Subtract(c(1), new Add(new Multiply(c(2), vx), vy))
|
||||
)
|
||||
.advanced(
|
||||
"(1 * (z / (y - x)))",
|
||||
"1 * (z / (y - x))",
|
||||
(x, y, z) -> 1 * (z / (y - x)),
|
||||
new Multiply(c(1), new Divide(vz, new Subtract(vy, vx)))
|
||||
)
|
||||
.advanced(
|
||||
"(z / (z + (1 + 1)))",
|
||||
"z / (z + 1 + 1)",
|
||||
(x, y, z) -> z / (z + 1 + 1),
|
||||
new Divide(vz, new Add(vz, new Add(c(1), c(1))))
|
||||
)
|
||||
.advanced(
|
||||
"((2 * x) + (1 + 1))",
|
||||
"2 * x + 1 + 1",
|
||||
(x, y, z) -> 2 * x + 1 + 1,
|
||||
new Add(new Multiply(c(2), vx), new Add(c(1), c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"((1 + 1) - (1 + 1))",
|
||||
"1 + 1 - (1 + 1)",
|
||||
(x, y, z) -> 1 + 1 - (1 + 1),
|
||||
new Subtract(new Add(c(1), c(1)), new Add(c(1), c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"((y - x) * (2 / x))",
|
||||
"(y - x) * (2 / x)",
|
||||
(x, y, z) -> (y - x) * (2 / x),
|
||||
new Multiply(new Subtract(vy, vx), new Divide(c(2), vx))
|
||||
)
|
||||
.advanced(
|
||||
"((y - x) / (2 * x))",
|
||||
"(y - x) / (2 * x)",
|
||||
(x, y, z) -> (y - x) / (2 * x),
|
||||
new Divide(new Subtract(vy, vx), new Multiply(c(2), vx))
|
||||
)
|
||||
.advanced(
|
||||
"(((y - x) - 2) + 1)",
|
||||
"y - x - 2 + 1",
|
||||
(x, y, z) -> y - x - 2 + 1,
|
||||
new Add(new Subtract(new Subtract(vy, vx), c(2)), c(1))
|
||||
)
|
||||
.advanced(
|
||||
"(((2 * x) + y) - z)",
|
||||
"2 * x + y - z",
|
||||
(x, y, z) -> 2 * x + y - z,
|
||||
new Subtract(new Add(new Multiply(c(2), vx), vy), vz)
|
||||
)
|
||||
.advanced(
|
||||
"(((1 + 1) / x) * 2)",
|
||||
"(1 + 1) / x * 2",
|
||||
(x, y, z) -> ((1 + 1) / x) * 2,
|
||||
new Multiply(new Divide(new Add(c(1), c(1)), vx), c(2))
|
||||
)
|
||||
.advanced(
|
||||
"((z / (y - x)) / x)",
|
||||
"z / (y - x) / x",
|
||||
(x, y, z) -> z / (y - x) / x,
|
||||
new Divide(new Divide(vz, new Subtract(vy, vx)), vx)
|
||||
);
|
||||
}
|
||||
|
||||
private static Const c(final Integer c) {
|
||||
return TYPE.constant(c);
|
||||
}
|
||||
|
||||
Selector SELECTOR = ExpressionTest.SELECTOR.variant(
|
||||
"Triple",
|
||||
ExpressionTest.v(TripleExpression::tester)
|
||||
);
|
||||
|
||||
static void main(final String... args) {
|
||||
TripleExpression.SELECTOR.main(args);
|
||||
}
|
||||
}
|
||||
118
java/expression/Variable.java
Normal file
118
java/expression/Variable.java
Normal file
@@ -0,0 +1,118 @@
|
||||
package expression;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class Variable extends AbstractExpression {
|
||||
|
||||
private final String name;
|
||||
private final int index;
|
||||
|
||||
public Variable(String name) {
|
||||
this.name = name;
|
||||
this.index = -1;
|
||||
}
|
||||
|
||||
public Variable(int index) {
|
||||
this.index = index;
|
||||
this.name = "$" + index;
|
||||
}
|
||||
|
||||
public Variable(int index, String name) {
|
||||
this.index = index;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int evaluate(int x) {
|
||||
if (index >= 0) {
|
||||
if (index == 0) return x;
|
||||
throw new IllegalStateException(
|
||||
"Positional variable $" +
|
||||
index +
|
||||
" cannot be evaluated with a single value"
|
||||
);
|
||||
}
|
||||
if ("x".equals(name)) return x;
|
||||
throw new IllegalStateException(
|
||||
"Variable '" + name + "' is not 'x'; use evaluate(x, y, z) instead"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int evaluate(int x, int y, int z) {
|
||||
if (index >= 0) {
|
||||
return switch (index) {
|
||||
case 0 -> x;
|
||||
case 1 -> y;
|
||||
case 2 -> z;
|
||||
default -> throw new IndexOutOfBoundsException(
|
||||
"Variable index " +
|
||||
index +
|
||||
" out of range for triple evaluate"
|
||||
);
|
||||
};
|
||||
}
|
||||
return switch (name) {
|
||||
case "x" -> x;
|
||||
case "y" -> y;
|
||||
case "z" -> z;
|
||||
default -> throw new IllegalStateException(
|
||||
"Unknown variable: " + name
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int evaluate(List<Integer> vars) {
|
||||
return vars.get(resolvedIndex());
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger evaluateBi(List<BigInteger> vars) {
|
||||
return vars.get(resolvedIndex());
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal evaluateBd(List<BigDecimal> vars) {
|
||||
return vars.get(resolvedIndex());
|
||||
}
|
||||
|
||||
private int resolvedIndex() {
|
||||
if (index >= 0) return index;
|
||||
return switch (name) {
|
||||
case "x" -> 0;
|
||||
case "y" -> 1;
|
||||
case "z" -> 2;
|
||||
default -> throw new IllegalStateException(
|
||||
"Unknown variable: " + name
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toMiniString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Variable)) return false;
|
||||
return name.equals(((Variable) obj).name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name.hashCode();
|
||||
}
|
||||
}
|
||||
32
java/expression/common/Expr.java
Normal file
32
java/expression/common/Expr.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package expression.common;
|
||||
|
||||
import base.Functional;
|
||||
import base.Pair;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public record Expr<C, V>(Node<C> node, List<Pair<String, V>> variables) {
|
||||
public <T> List<Pair<String, T>> variables(final BiFunction<String, V, T> f) {
|
||||
return Functional.map(
|
||||
variables,
|
||||
variable -> variable.second(f.apply(variable.first(), variable.second()))
|
||||
);
|
||||
}
|
||||
|
||||
public <T> Expr<C, T> convert(final BiFunction<String, V, T> f) {
|
||||
return of(node, variables(f));
|
||||
}
|
||||
|
||||
public Expr<C, V> node(final Function<Node<C>, Node<C>> f) {
|
||||
return of(f.apply(node), variables);
|
||||
}
|
||||
|
||||
public static <C, V> Expr<C, V> of(final Node<C> node, final List<Pair<String, V>> variables) {
|
||||
return new Expr<>(node, variables);
|
||||
}
|
||||
}
|
||||
94
java/expression/common/ExpressionKind.java
Normal file
94
java/expression/common/ExpressionKind.java
Normal file
@@ -0,0 +1,94 @@
|
||||
package expression.common;
|
||||
|
||||
import base.ExtendedRandom;
|
||||
import base.Functional;
|
||||
import base.Pair;
|
||||
import expression.ToMiniString;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class ExpressionKind<E extends ToMiniString, C> {
|
||||
private final Type<C> type;
|
||||
private final Class<E> kind;
|
||||
private final Variables<E> variables;
|
||||
private final Evaluator<E, C> evaluator;
|
||||
|
||||
public ExpressionKind(
|
||||
final Type<C> type,
|
||||
final Class<E> kind,
|
||||
final Variables<E> variables,
|
||||
final Evaluator<E, C> evaluator
|
||||
) {
|
||||
this.type = type;
|
||||
this.kind = kind;
|
||||
this.variables = variables;
|
||||
this.evaluator = evaluator;
|
||||
}
|
||||
|
||||
public ExpressionKind(
|
||||
final Type<C> type,
|
||||
final Class<E> kind,
|
||||
final List<Pair<String, E>> variables,
|
||||
final Evaluator<E, C> evaluator
|
||||
) {
|
||||
this(type, kind, (r, c) -> variables, evaluator);
|
||||
}
|
||||
|
||||
public C evaluate(final E expression, final List<String> variables, final List<C> values) throws Exception {
|
||||
return evaluator.evaluate(expression, variables, values);
|
||||
}
|
||||
|
||||
public E cast(final Object expression) {
|
||||
return kind.cast(expression);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return kind.getSimpleName();
|
||||
}
|
||||
|
||||
public E constant(final C value) {
|
||||
return cast(type.constant(value));
|
||||
}
|
||||
|
||||
public C randomValue(final ExtendedRandom random) {
|
||||
return type.randomValue(random);
|
||||
}
|
||||
|
||||
public List<List<C>> allValues(final int length, final List<Integer> values) {
|
||||
return Functional.allValues(fromInts(values), length);
|
||||
}
|
||||
|
||||
public List<C> fromInts(final List<Integer> values) {
|
||||
return Functional.map(values, this::fromInt);
|
||||
}
|
||||
|
||||
public C fromInt(final int value) {
|
||||
return type.fromInt(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return kind.getName();
|
||||
}
|
||||
|
||||
public ExpressionKind<E, C> withVariables(final Variables<E> variables) {
|
||||
return new ExpressionKind<>(type, kind, variables, evaluator);
|
||||
}
|
||||
|
||||
public Variables<E> variables() {
|
||||
return variables;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Variables<E> {
|
||||
List<Pair<String, E>> generate(final ExtendedRandom random, final int count);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Evaluator<E, R> {
|
||||
R evaluate(final E expression, final List<String> vars, final List<R> values) throws Exception;
|
||||
}
|
||||
}
|
||||
173
java/expression/common/Generator.java
Normal file
173
java/expression/common/Generator.java
Normal file
@@ -0,0 +1,173 @@
|
||||
package expression.common;
|
||||
|
||||
import base.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class Generator<C, E> {
|
||||
private final Supplier<C> constant;
|
||||
private final List<Named<Integer>> ops;
|
||||
private final ExpressionKind.Variables<E> variables;
|
||||
private final Set<String> forbidden;
|
||||
private final ExtendedRandom random;
|
||||
private final List<Function<List<Node<C>>, Stream<Node<C>>>> basicTests;
|
||||
|
||||
public Generator(
|
||||
final Supplier<C> constant,
|
||||
final List<Named<Integer>> ops,
|
||||
final ExpressionKind.Variables<E> variables,
|
||||
final Set<String> forbidden,
|
||||
final ExtendedRandom random,
|
||||
final List<Function<List<Node<C>>, Stream<Node<C>>>> basicTests
|
||||
) {
|
||||
this.constant = constant;
|
||||
this.ops = List.copyOf(ops);
|
||||
this.variables = variables;
|
||||
this.forbidden = Set.copyOf(forbidden);
|
||||
this.random = random;
|
||||
this.basicTests = List.copyOf(basicTests);
|
||||
}
|
||||
|
||||
public static <C> Builder<C> builder(final Supplier<C> constant, final ExtendedRandom random) {
|
||||
return new Builder<>(random, constant);
|
||||
}
|
||||
|
||||
public void testRandom(
|
||||
final TestCounter counter,
|
||||
final int denominator,
|
||||
final Consumer<Expr<C, E>> consumer
|
||||
) {
|
||||
final int d = Math.max(TestCounter.DENOMINATOR, denominator);
|
||||
testRandom(counter, consumer, 1, 100, 100 / d, (vars, depth) -> generateFullDepth(vars, Math.min(depth, 3)));
|
||||
testRandom(counter, consumer, 2, 1000 / d, 1, this::generateSize);
|
||||
testRandom(counter, consumer, 3, 12, 100 / d, this::generateFullDepth);
|
||||
testRandom(counter, consumer, 4, 777 / d, 1, this::generatePartialDepth);
|
||||
}
|
||||
|
||||
private void testRandom(
|
||||
final TestCounter counter,
|
||||
final Consumer<Expr<C, E>> consumer,
|
||||
final int seq,
|
||||
final int levels,
|
||||
final int perLevel,
|
||||
final BiFunction<List<Node<C>>, Integer, Node<C>> generator
|
||||
) {
|
||||
counter.scope("Random tests #" + seq, () -> {
|
||||
final int total = levels * perLevel;
|
||||
int generated = 0;
|
||||
for (int level = 0; level < levels; level++) {
|
||||
for (int j = 0; j < perLevel; j++) {
|
||||
if (generated % 100 == 0) {
|
||||
progress(counter, total, generated);
|
||||
}
|
||||
generated++;
|
||||
|
||||
final List<Pair<String, E>> vars = variables(random.nextInt(10) + 1);
|
||||
consumer.accept(Expr.of(generator.apply(Functional.map(vars, v -> Node.op(v.first())), level), vars));
|
||||
}
|
||||
}
|
||||
progress(counter, generated, total);
|
||||
});
|
||||
}
|
||||
|
||||
private static void progress(final TestCounter counter, final int total, final int generated) {
|
||||
counter.format("Completed %4d out of %d%n", generated, total);
|
||||
}
|
||||
|
||||
private Node<C> generate(
|
||||
final List<Node<C>> variables,
|
||||
final boolean nullary,
|
||||
final Supplier<Node<C>> unary,
|
||||
final Supplier<Pair<Node<C>, Node<C>>> binary
|
||||
) {
|
||||
if (nullary || ops.isEmpty()) {
|
||||
return random.nextBoolean() ? random.randomItem(variables) : Node.constant(constant.get());
|
||||
} else {
|
||||
final Named<Integer> op = random.randomItem(ops);
|
||||
if (Math.abs(op.value()) == 1) {
|
||||
return Node.op(op.name(), (op.value() + 1) >> 1, unary.get());
|
||||
} else {
|
||||
final Pair<Node<C>, Node<C>> pair = binary.get();
|
||||
return Node.op(op.name(), pair.first(), pair.second());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Node<C> generate(final List<Node<C>> variables, final boolean nullary, final Supplier<Node<C>> child) {
|
||||
return generate(variables, nullary, child, () -> Pair.of(child.get(), child.get()));
|
||||
}
|
||||
|
||||
private Node<C> generateFullDepth(final List<Node<C>> variables, final int depth) {
|
||||
return generate(variables, depth == 0, () -> generateFullDepth(variables, depth - 1));
|
||||
}
|
||||
|
||||
private Node<C> generatePartialDepth(final List<Node<C>> variables, final int depth) {
|
||||
return generate(variables, depth == 0, () -> generatePartialDepth(variables, random.nextInt(depth)));
|
||||
}
|
||||
|
||||
private Node<C> generateSize(final List<Node<C>> variables, final int size) {
|
||||
final int first = size <= 1 ? 0 : random.nextInt(size);
|
||||
return generate(
|
||||
variables,
|
||||
size == 0,
|
||||
() -> generateSize(variables, size - 1),
|
||||
() -> Pair.of(
|
||||
generateSize(variables, first),
|
||||
generateSize(variables, size - 1 - first)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public void testBasic(final Consumer<Expr<C, E>> consumer) {
|
||||
basicTests.forEach(test -> {
|
||||
final List<Pair<String, E>> vars = variables(random.nextInt(5) + 3);
|
||||
test.apply(Functional.map(vars, v -> Node.op(v.first())))
|
||||
.map(node -> Expr.of(node, vars))
|
||||
.forEachOrdered(consumer);
|
||||
});
|
||||
}
|
||||
|
||||
public List<Pair<String, E>> variables(final int count) {
|
||||
List<Pair<String, E>> vars;
|
||||
do {
|
||||
vars = variables.generate(random, count);
|
||||
} while (vars.stream().map(Pair::first).anyMatch(forbidden::contains));
|
||||
return vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public static final class Builder<C> {
|
||||
private final ExtendedRandom random;
|
||||
private final Supplier<C> constant;
|
||||
|
||||
private final List<Named<Integer>> ops = new ArrayList<>();
|
||||
private final Set<String> forbidden = new HashSet<>();
|
||||
|
||||
private Builder(final ExtendedRandom random, final Supplier<C> constant) {
|
||||
this.random = random;
|
||||
this.constant = constant;
|
||||
}
|
||||
|
||||
public void add(final String name, final int arity) {
|
||||
ops.add(Named.of(name, arity));
|
||||
forbidden.add(name);
|
||||
}
|
||||
|
||||
public <E> Generator<C, E> build(
|
||||
final ExpressionKind.Variables<E> variables,
|
||||
final List<Function<List<Node<C>>, Stream<Node<C>>>> basicTests
|
||||
) {
|
||||
return new Generator<>(constant, ops, variables, forbidden, random, basicTests);
|
||||
}
|
||||
}
|
||||
}
|
||||
106
java/expression/common/Node.java
Normal file
106
java/expression/common/Node.java
Normal file
@@ -0,0 +1,106 @@
|
||||
package expression.common;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public abstract class Node<T> {
|
||||
private Node() {
|
||||
}
|
||||
|
||||
public abstract <R> R get(Const<T, R> con, Nullary<R> nul, Unary<Node<T>, R> un, Binary<Node<T>, R> bin);
|
||||
public abstract <R> R cata(Const<T, R> con, Nullary<R> nul, Unary<R, R> un, Binary<R, R> bin);
|
||||
|
||||
public final String toPolish() {
|
||||
return cata(
|
||||
T::toString,
|
||||
name -> name,
|
||||
(name, priority, a) -> a + " " + name + ":1",
|
||||
(name, a1, a2) -> a1 + " " + a2 + " " + name + ":2"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
return cata(
|
||||
T::toString,
|
||||
name -> name,
|
||||
(name, priority, a) -> name.equals("[") ? "[" + a + "]" :
|
||||
(priority & 1) == 1 ? "(" + name + " " + a + ")" : "(" + a + " " + name + ")",
|
||||
(name, a1, a2) -> "(" + a1 + " " + name + " " + a2 + ")"
|
||||
);
|
||||
}
|
||||
|
||||
public static <T> Node<T> constant(final T value) {
|
||||
return new Node<>() {
|
||||
@Override
|
||||
public <R> R get(final Const<T, R> con, final Nullary<R> nul, final Unary<Node<T>, R> un, final Binary<Node<T>, R> bin) {
|
||||
return con.apply(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R> R cata(final Const<T, R> con, final Nullary<R> nul, final Unary<R, R> un, final Binary<R, R> bin) {
|
||||
return con.apply(value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Node<T> op(final String name) {
|
||||
return new Node<>() {
|
||||
@Override
|
||||
public <R> R get(final Const<T, R> con, final Nullary<R> nul, final Unary<Node<T>, R> un, final Binary<Node<T>, R> bin) {
|
||||
return nul.apply(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R> R cata(final Const<T, R> con, final Nullary<R> nul, final Unary<R, R> un, final Binary<R, R> bin) {
|
||||
return nul.apply(name);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Node<T> op(final String name, final int priority, final Node<T> arg) {
|
||||
return new Node<>() {
|
||||
@Override
|
||||
public <R> R get(final Const<T, R> con, final Nullary<R> nul, final Unary<Node<T>, R> un, final Binary<Node<T>, R> bin) {
|
||||
return un.apply(name, priority, arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R> R cata(final Const<T, R> con, final Nullary<R> nul, final Unary<R, R> un, final Binary<R, R> bin) {
|
||||
return un.apply(name, priority, arg.cata(con, nul, un, bin));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Node<T> op(final String name, final Node<T> arg1, final Node<T> arg2) {
|
||||
return new Node<>() {
|
||||
@Override
|
||||
public <R> R get(final Const<T, R> con, final Nullary<R> nul, final Unary<Node<T>, R> un, final Binary<Node<T>, R> bin) {
|
||||
return bin.apply(name, arg1, arg2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R> R cata(final Const<T, R> con, final Nullary<R> nul, final Unary<R, R> un, final Binary<R, R> bin) {
|
||||
return bin.apply(name, arg1.cata(con, nul, un, bin), arg2.cata(con, nul, un, bin));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Const<T, R> extends Function<T, R> {}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Nullary<R> extends Function<String, R> {}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Unary<T, R> {
|
||||
R apply(String name, int priority, T arg);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Binary<T, R> {
|
||||
R apply(String name, T arg1, T arg2);
|
||||
}
|
||||
}
|
||||
96
java/expression/common/NodeRenderer.java
Normal file
96
java/expression/common/NodeRenderer.java
Normal file
@@ -0,0 +1,96 @@
|
||||
package expression.common;
|
||||
|
||||
import base.ExtendedRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class NodeRenderer<C> {
|
||||
public static final String PAREN = "[";
|
||||
public static final List<Paren> DEFAULT_PARENS = List.of(paren("(", ")"));
|
||||
|
||||
public static final Mode MINI_MODE = Mode.SIMPLE_MINI; // Replace by TRUE_MINI for some challenge;
|
||||
public static final Settings FULL = Mode.FULL.settings(0);
|
||||
public static final Settings FULL_EXTRA = Mode.FULL.settings(Integer.MAX_VALUE / 4);
|
||||
public static final Settings SAME = Mode.SAME.settings(0);
|
||||
public static final Settings MINI = MINI_MODE.settings(0);
|
||||
public static final Settings TRUE_MINI = Mode.TRUE_MINI.settings(0);
|
||||
|
||||
private final Renderer<C, Settings, Node<C>> renderer;
|
||||
private final Map<String, String> brackets;
|
||||
private final ExtendedRandom random;
|
||||
|
||||
public NodeRenderer(
|
||||
final Renderer<C, Settings, Node<C>> renderer,
|
||||
final Map<String, String> brackets,
|
||||
final ExtendedRandom random
|
||||
) {
|
||||
this.renderer = renderer;
|
||||
this.brackets = Map.copyOf(brackets);
|
||||
this.random = random;
|
||||
}
|
||||
|
||||
public static <C> Node<C> paren(final boolean condition, final Node<C> node) {
|
||||
return condition ? Node.op(PAREN, 1, node) : node;
|
||||
}
|
||||
|
||||
public static Paren paren(final String open, final String close) {
|
||||
return new Paren(open, close);
|
||||
}
|
||||
|
||||
public Node<C> renderToNode(final Settings settings, final Expr<C, ?> expr) {
|
||||
final Expr<C, Node<C>> convert = expr.convert((name, variable) -> Node.op(name));
|
||||
return renderer.render(convert, settings);
|
||||
}
|
||||
|
||||
public String render(final Node<C> node, final List<Paren> parens) {
|
||||
return node.cata(
|
||||
String::valueOf,
|
||||
name -> name,
|
||||
(name, priority, arg) ->
|
||||
name == PAREN ? random.randomItem(parens).apply(arg) :
|
||||
priority == Integer.MAX_VALUE ? name + arg + brackets.get(name) :
|
||||
(priority & 1) == 1 ? name + arg :
|
||||
arg + name,
|
||||
(name, a, b) -> a + " " + name + " " + b
|
||||
);
|
||||
}
|
||||
|
||||
public String render(final Expr<C, ?> expr, final Settings settings) {
|
||||
return render(renderToNode(settings, expr), settings.parens());
|
||||
}
|
||||
|
||||
public enum Mode {
|
||||
FULL, SAME, TRUE_MINI, SIMPLE_MINI;
|
||||
|
||||
public Settings settings(final int limit) {
|
||||
return new Settings(this, limit);
|
||||
}
|
||||
}
|
||||
|
||||
public record Paren(String open, String close) {
|
||||
String apply(final String expression) {
|
||||
return open() + expression + close();
|
||||
}
|
||||
}
|
||||
|
||||
public record Settings(Mode mode, int limit, List<Paren> parens) {
|
||||
public Settings(final Mode mode, final int limit) {
|
||||
this(mode, limit, DEFAULT_PARENS);
|
||||
}
|
||||
|
||||
public <C> Node<C> extra(Node<C> node, final ExtendedRandom random) {
|
||||
while (random.nextInt(Integer.MAX_VALUE) < limit) {
|
||||
node = paren(true, node);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
public Settings withParens(final List<Paren> parens) {
|
||||
return this.parens.equals(parens) ? this : new Settings(mode, limit, List.copyOf(parens));
|
||||
}
|
||||
}
|
||||
}
|
||||
145
java/expression/common/NodeRendererBuilder.java
Normal file
145
java/expression/common/NodeRendererBuilder.java
Normal file
@@ -0,0 +1,145 @@
|
||||
package expression.common;
|
||||
|
||||
import base.ExtendedRandom;
|
||||
import base.Functional;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
|
||||
public class NodeRendererBuilder<C> {
|
||||
private final Renderer.Builder<C, NodeRenderer.Settings, Node<C>> nodeRenderer = Renderer.builder(Node::constant);
|
||||
private final Map<String, Priority> priorities = new HashMap<>();
|
||||
private final Map<String, String> brackets = new HashMap<>();
|
||||
private final ExtendedRandom random;
|
||||
|
||||
public NodeRendererBuilder(final ExtendedRandom random) {
|
||||
this.random = random;
|
||||
nodeRenderer.unary(NodeRenderer.PAREN, (mode, arg) -> NodeRenderer.paren(true, arg));
|
||||
}
|
||||
|
||||
public void unary(final String name, final int priority) {
|
||||
final String space = name.equals("-") || Character.isLetter(name.charAt(0)) ? " " : "";
|
||||
nodeRenderer.unary(
|
||||
name,
|
||||
(settings, arg) -> settings.extra(Node.op(name, priority, inner(settings, priority, arg, space)), random)
|
||||
);
|
||||
}
|
||||
|
||||
public void unary(final String left, final String right) {
|
||||
brackets.put(left, right);
|
||||
nodeRenderer.unary(
|
||||
left,
|
||||
(settings, arg) -> settings.extra(Node.op(left, Integer.MAX_VALUE, arg), random)
|
||||
);
|
||||
}
|
||||
|
||||
private Node<C> inner(final NodeRenderer.Settings settings, final int priority, final Node<C> arg, final String space) {
|
||||
if (settings.mode() == NodeRenderer.Mode.FULL) {
|
||||
return NodeRenderer.paren(true, arg);
|
||||
} else {
|
||||
final String op = arg.get(
|
||||
c -> space,
|
||||
n -> space,
|
||||
(n, p, a) ->
|
||||
priority > unaryPriority(arg) ? NodeRenderer.PAREN :
|
||||
NodeRenderer.PAREN.equals(n) ? "" :
|
||||
space,
|
||||
(n, a, b) -> NodeRenderer.PAREN
|
||||
);
|
||||
return op.isEmpty() ? arg : Node.op(op, Priority.MAX.priority | 1, arg);
|
||||
}
|
||||
}
|
||||
|
||||
private static <C> Integer unaryPriority(final Node<C> node) {
|
||||
return node.get(c -> Integer.MAX_VALUE, n -> Integer.MAX_VALUE, (n, p, a) -> p, (n, a, b) -> Integer.MIN_VALUE);
|
||||
}
|
||||
|
||||
public void binary(final String name, final int priority) {
|
||||
final Priority mp = new Priority(name, priority);
|
||||
priorities.put(name, mp);
|
||||
|
||||
nodeRenderer.binary(name, (settings, l, r) -> settings.extra(process(settings, mp, l, r), random));
|
||||
}
|
||||
|
||||
private Node<C> process(final NodeRenderer.Settings settings, final Priority mp, final Node<C> l, final Node<C> r) {
|
||||
if (settings.mode() == NodeRenderer.Mode.FULL) {
|
||||
return NodeRenderer.paren(true, op(mp, l, r));
|
||||
}
|
||||
|
||||
final Priority lp = priority(l);
|
||||
final Priority rp = priority(r);
|
||||
|
||||
final int rc = rp.compareLevels(mp);
|
||||
|
||||
// :NOTE: Especially ugly code, do not replicate
|
||||
final boolean advanced = settings.mode() == NodeRenderer.Mode.SAME
|
||||
|| mp.has(2)
|
||||
|| mp.has(1) && (mp != rp || (settings.mode() == NodeRenderer.Mode.TRUE_MINI && hasOther(r, rp)));
|
||||
|
||||
final Node<C> al = NodeRenderer.paren(lp.compareLevels(mp) < 0, l);
|
||||
if (rc == 0 && !advanced) {
|
||||
return get(r, null, (n, a, b) -> rp.op(mp.op(al, a), b));
|
||||
} else {
|
||||
return mp.op(al, NodeRenderer.paren(rc == 0 && advanced || rc < 0, r));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasOther(final Node<C> node, final Priority priority) {
|
||||
return get(node, () -> false, (name, l, r) -> {
|
||||
final Priority p = Functional.get(priorities, name);
|
||||
if (p.compareLevels(priority) != 0) {
|
||||
return false;
|
||||
}
|
||||
return p != priority || hasOther(l, priority);
|
||||
});
|
||||
}
|
||||
|
||||
private Node<C> op(final Priority mp, final Node<C> l, final Node<C> r) {
|
||||
return mp.op(l, r);
|
||||
}
|
||||
|
||||
private Priority priority(final Node<C> node) {
|
||||
return get(node, () -> Priority.MAX, (n, a, b) -> Functional.get(priorities, n));
|
||||
}
|
||||
|
||||
private <R> R get(final Node<C> node, final Supplier<R> common, final Node.Binary<Node<C>, R> binary) {
|
||||
return node.get(
|
||||
c -> common.get(),
|
||||
n -> common.get(),
|
||||
(n, p, a) -> common.get(),
|
||||
binary
|
||||
);
|
||||
}
|
||||
|
||||
public NodeRenderer<C> build() {
|
||||
return new NodeRenderer<>(nodeRenderer.build(), brackets, random);
|
||||
}
|
||||
|
||||
// :NOTE: Especially ugly bit-fiddling, do not replicate
|
||||
private record Priority(String op, int priority) {
|
||||
private static final int Q = 3;
|
||||
private static final Priority MAX = new Priority("MAX", Integer.MAX_VALUE - Q);
|
||||
|
||||
private int compareLevels(final Priority that) {
|
||||
return (priority | Q) - (that.priority | Q);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Priority(%s, %d, %d)", op, priority | Q, priority & Q);
|
||||
}
|
||||
|
||||
public <C> Node<C> op(final Node<C> l, final Node<C> r) {
|
||||
return Node.op(op, l, r);
|
||||
}
|
||||
|
||||
private boolean has(final int value) {
|
||||
return (priority & Q) == value;
|
||||
}
|
||||
}
|
||||
}
|
||||
60
java/expression/common/Reason.java
Normal file
60
java/expression/common/Reason.java
Normal file
@@ -0,0 +1,60 @@
|
||||
package expression.common;
|
||||
|
||||
import base.Either;
|
||||
|
||||
import java.util.function.LongUnaryOperator;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class Reason {
|
||||
public static final Reason OVERFLOW = new Reason("Overflow");
|
||||
public static final Reason DBZ = new Reason("Division by zero");
|
||||
|
||||
private final String description;
|
||||
|
||||
public Reason(final String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public static <T> Either<Reason, T> eval(final Supplier<T> action) {
|
||||
try {
|
||||
return Either.right(action.get());
|
||||
} catch (final ReasonException e) {
|
||||
return Either.left(e.reason);
|
||||
}
|
||||
}
|
||||
|
||||
public static int overflow(final long value) {
|
||||
return value < Integer.MIN_VALUE || Integer.MAX_VALUE < value
|
||||
? OVERFLOW.error()
|
||||
: (int) value;
|
||||
}
|
||||
|
||||
public <T> T error() {
|
||||
throw new ReasonException(this);
|
||||
}
|
||||
|
||||
public LongUnaryOperator less(final long limit, final LongUnaryOperator op) {
|
||||
return a -> a < limit ? error() : op.applyAsLong(a);
|
||||
}
|
||||
|
||||
public LongUnaryOperator greater(final int limit, final LongUnaryOperator op) {
|
||||
return a -> a > limit ? error() : op.applyAsLong(a);
|
||||
}
|
||||
|
||||
private static class ReasonException extends RuntimeException {
|
||||
private final Reason reason;
|
||||
|
||||
public ReasonException(final Reason reason) {
|
||||
super(reason.description);
|
||||
this.reason = reason;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Reason(%s)", description);
|
||||
}
|
||||
}
|
||||
60
java/expression/common/Renderer.java
Normal file
60
java/expression/common/Renderer.java
Normal file
@@ -0,0 +1,60 @@
|
||||
package expression.common;
|
||||
|
||||
import base.Functional;
|
||||
import base.Pair;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public interface Renderer<C, S, R> {
|
||||
static <C, S, R> Builder<C, S, R> builder(final Node.Const<C, R> constant) {
|
||||
return new Builder<>(constant);
|
||||
}
|
||||
|
||||
R render(final Expr<C, R> expr, final S settings);
|
||||
|
||||
@FunctionalInterface
|
||||
interface UnaryOperator<S, R> {
|
||||
R apply(S settings, R arg);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface BinaryOperator<S, R> {
|
||||
R apply(S settings, R arg1, R arg2);
|
||||
}
|
||||
|
||||
final class Builder<C, S, R> {
|
||||
private final Node.Const<C, R> constant;
|
||||
private final Map<String, UnaryOperator<S, R>> unary = new HashMap<>();
|
||||
private final Map<String, BinaryOperator<S, R>> binary = new HashMap<>();
|
||||
|
||||
private Builder(final Node.Const<C, R> constant) {
|
||||
this.constant = constant;
|
||||
}
|
||||
|
||||
public void unary(final String name, final UnaryOperator<S, R> op) {
|
||||
unary.put(name, op);
|
||||
}
|
||||
|
||||
public void binary(final String name, final BinaryOperator<S, R> op) {
|
||||
binary.put(name, op);
|
||||
}
|
||||
|
||||
public Renderer<C, S, R> build() {
|
||||
return (expr, settings) -> {
|
||||
final Map<String, R> vars = expr.variables().stream()
|
||||
.collect(Collectors.toMap(Pair::first, Pair::second));
|
||||
return expr.node().cata(
|
||||
constant,
|
||||
name -> Functional.get(vars, name),
|
||||
(name, p, arg) -> Functional.get(unary, name).apply(settings, arg),
|
||||
(name, arg1, arg2) -> Functional.get(binary, name).apply(settings, arg1, arg2)
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
56
java/expression/common/TestGenerator.java
Normal file
56
java/expression/common/TestGenerator.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package expression.common;
|
||||
|
||||
import base.Pair;
|
||||
import base.TestCounter;
|
||||
import expression.ToMiniString;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class TestGenerator<C, E extends ToMiniString> {
|
||||
private final Generator<C, E> generator;
|
||||
private final NodeRenderer<C> renderer;
|
||||
|
||||
public TestGenerator(final Generator<C, E> generator, final NodeRenderer<C> renderer) {
|
||||
this.generator = generator;
|
||||
this.renderer = renderer;
|
||||
}
|
||||
|
||||
public void testBasic(final Consumer<Test<C, E>> test) {
|
||||
generator.testBasic(consumer(test));
|
||||
}
|
||||
|
||||
public void testRandom(final TestCounter counter, final int denominator, final Consumer<Test<C, E>> test) {
|
||||
generator.testRandom(counter, denominator, consumer(test));
|
||||
}
|
||||
|
||||
private Consumer<Expr<C, E>> consumer(final Consumer<TestGenerator.Test<C, E>> consumer) {
|
||||
return expr -> consumer.accept(new TestGenerator.Test<>(expr, renderer));
|
||||
}
|
||||
|
||||
|
||||
public List<Pair<String, E>> variables(final int count) {
|
||||
return generator.variables(count);
|
||||
}
|
||||
|
||||
public String render(final Expr<C, ?> expr, final NodeRenderer.Settings settings) {
|
||||
return renderer.render(expr, settings);
|
||||
}
|
||||
|
||||
public static class Test<C, E> {
|
||||
public final Expr<C, E> expr;
|
||||
private final Map<NodeRenderer.Settings, String> rendered = new HashMap<>();
|
||||
private final NodeRenderer<C> renderer;
|
||||
|
||||
public Test(final Expr<C, E> expr, final NodeRenderer<C> renderer) {
|
||||
this.expr = expr;
|
||||
this.renderer = renderer;
|
||||
}
|
||||
|
||||
public String render(final NodeRenderer.Settings settings) {
|
||||
return rendered.computeIfAbsent(settings, s -> renderer.render(expr, s));
|
||||
}
|
||||
}
|
||||
}
|
||||
145
java/expression/common/TestGeneratorBuilder.java
Normal file
145
java/expression/common/TestGeneratorBuilder.java
Normal file
@@ -0,0 +1,145 @@
|
||||
package expression.common;
|
||||
|
||||
import base.ExtendedRandom;
|
||||
import base.Functional;
|
||||
import expression.ToMiniString;
|
||||
import expression.common.ExpressionKind.Variables;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class TestGeneratorBuilder<C> {
|
||||
private final ExtendedRandom random;
|
||||
|
||||
private final Generator.Builder<C> generator;
|
||||
private final NodeRendererBuilder<C> renderer;
|
||||
|
||||
private final List<Function<List<Node<C>>, Stream<Node<C>>>> basicTests = new ArrayList<>();
|
||||
private final List<Node<C>> consts;
|
||||
private final boolean verbose;
|
||||
|
||||
public TestGeneratorBuilder(
|
||||
final ExtendedRandom random,
|
||||
final Supplier<C> constant,
|
||||
final List<C> constants,
|
||||
final boolean verbose
|
||||
) {
|
||||
this.random = random;
|
||||
this.verbose = verbose;
|
||||
|
||||
generator = Generator.builder(constant, random);
|
||||
renderer = new NodeRendererBuilder<>(random);
|
||||
|
||||
consts = Functional.map(constants, Node::constant);
|
||||
basicTests.add(vars -> consts.stream());
|
||||
basicTests.add(List::stream);
|
||||
}
|
||||
|
||||
private Node<C> c() {
|
||||
return random.randomItem(consts);
|
||||
}
|
||||
|
||||
private Node<C> v(final List<Node<C>> variables) {
|
||||
return random.randomItem(variables);
|
||||
}
|
||||
|
||||
private static <C> Node<C> f(final String name, final int priority, final Node<C> arg) {
|
||||
return Node.op(name, priority, arg);
|
||||
}
|
||||
|
||||
private static <C> Node<C> f(final String left, final Node<C> arg) {
|
||||
return Node.op(left, Integer.MAX_VALUE, arg);
|
||||
}
|
||||
|
||||
private static <C> Node<C> f(final String name, final Node<C> arg1, final Node<C> arg2) {
|
||||
return Node.op(name, arg1, arg2);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
private void basicTests(final Function<List<Node<C>>, Node<C>>... tests) {
|
||||
Arrays.stream(tests).map(test -> test.andThen(Stream::of)).forEachOrdered(basicTests::add);
|
||||
}
|
||||
|
||||
public void unary(final String name, final int priority) {
|
||||
generator.add(name, (priority & 1) * 2 - 1);
|
||||
renderer.unary(name, priority);
|
||||
|
||||
if (verbose) {
|
||||
basicTests.add(vars -> Stream.concat(consts.stream(), vars.stream()).map(a -> f(name, priority, a)));
|
||||
} else {
|
||||
basicTests(vars -> f(name, priority, c()), vars -> f(name, priority, v(vars)));
|
||||
}
|
||||
|
||||
final Function<List<Node<C>>, Node<C>> p1 = vars -> f(name, priority, f(name, priority, f("+", v(vars), c())));
|
||||
final Function<List<Node<C>>, Node<C>> p2 = vars -> f("*", v(vars), f("*", v(vars), f(name, priority, c())));
|
||||
basicTests(
|
||||
vars -> f(name, priority, f("+", v(vars), v(vars))),
|
||||
vars -> f(name, priority, f(name, priority, v(vars))),
|
||||
vars -> f(name, priority, f("/", f(name, priority, v(vars)), f("+", v(vars), v(vars)))),
|
||||
p1,
|
||||
p2,
|
||||
vars -> f("+", p1.apply(vars), p2.apply(vars))
|
||||
);
|
||||
}
|
||||
|
||||
public void unary(final String left, final String right) {
|
||||
generator.add(left, 1);
|
||||
renderer.unary(left, right);
|
||||
|
||||
if (verbose) {
|
||||
basicTests.add(vars -> Stream.concat(consts.stream(), vars.stream()).map(a -> f(left, a)));
|
||||
} else {
|
||||
basicTests(vars -> f(left, c()), vars -> f(left, v(vars)));
|
||||
}
|
||||
|
||||
final Function<List<Node<C>>, Node<C>> p1 = vars -> f(left, f(left, f("+", v(vars), c())));
|
||||
final Function<List<Node<C>>, Node<C>> p2 = vars -> f("*", v(vars), f("*", v(vars), f(left, c())));
|
||||
basicTests(
|
||||
vars -> f(left, f("+", v(vars), v(vars))),
|
||||
vars -> f(left, f(left, v(vars))),
|
||||
vars -> f(left, f("/", f(left, v(vars)), f("+", v(vars), v(vars)))),
|
||||
p1,
|
||||
p2,
|
||||
vars -> f("+", p1.apply(vars), p2.apply(vars))
|
||||
);
|
||||
}
|
||||
|
||||
public void binary(final String name, final int priority) {
|
||||
generator.add(name, 2);
|
||||
renderer.binary(name, priority);
|
||||
|
||||
if (verbose) {
|
||||
basicTests.add(vars -> Stream.concat(consts.stream(), vars.stream().limit(3))
|
||||
.flatMap(a -> consts.stream().map(b -> f(name, a, b))));
|
||||
} else {
|
||||
basicTests(
|
||||
vars -> f(name, c(), c()),
|
||||
vars -> f(name, v(vars), c()),
|
||||
vars -> f(name, c(), v(vars)),
|
||||
vars -> f(name, v(vars), v(vars))
|
||||
);
|
||||
}
|
||||
|
||||
final Function<List<Node<C>>, Node<C>> p1 = vars -> f(name, f(name, f("+", v(vars), c()), v(vars)), v(vars));
|
||||
final Function<List<Node<C>>, Node<C>> p2 = vars -> f("*", v(vars), f("*", v(vars), f(name, c(), v(vars))));
|
||||
|
||||
basicTests(
|
||||
vars -> f(name, f(name, v(vars), v(vars)), v(vars)),
|
||||
vars -> f(name, v(vars), f(name, v(vars), v(vars))),
|
||||
vars -> f(name, f(name, v(vars), v(vars)), f(name, v(vars), v(vars))),
|
||||
vars -> f(name, f("-", f(name, v(vars), v(vars)), c()), f("+", v(vars), v(vars))),
|
||||
p1,
|
||||
p2,
|
||||
vars -> f("+", p1.apply(vars), p2.apply(vars))
|
||||
);
|
||||
}
|
||||
|
||||
public <E extends ToMiniString> TestGenerator<C,E> build(final Variables<E> variables) {
|
||||
return new TestGenerator<>(generator.build(variables, basicTests), renderer.build());
|
||||
}
|
||||
}
|
||||
51
java/expression/common/Type.java
Normal file
51
java/expression/common/Type.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package expression.common;
|
||||
|
||||
import base.Asserts;
|
||||
import base.ExtendedRandom;
|
||||
import expression.Const;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class Type<C> {
|
||||
private final IntFunction<C> fromInt;
|
||||
private final Function<ExtendedRandom, C> random;
|
||||
private final Function<C, Const> constant;
|
||||
|
||||
public Type(final IntFunction<C> fromInt, final Function<ExtendedRandom, C> random, final Class<?> type) {
|
||||
this.fromInt = fromInt;
|
||||
this.random = random;
|
||||
|
||||
try {
|
||||
final MethodHandle constructor = MethodHandles.publicLookup()
|
||||
.findConstructor(Const.class, MethodType.methodType(void.class, type));
|
||||
constant = c -> {
|
||||
try {
|
||||
return (Const) constructor.invoke(c);
|
||||
} catch (final Throwable e) {
|
||||
throw Asserts.error("Cannot create new Const(%s): %s", c, e);
|
||||
}
|
||||
};
|
||||
} catch (final IllegalAccessException | NoSuchMethodException e) {
|
||||
throw Asserts.error("Cannot find constructor Const(%s): %s", type, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Const constant(final C value) {
|
||||
return constant.apply(value);
|
||||
}
|
||||
|
||||
public C fromInt(final int value) {
|
||||
return fromInt.apply(value);
|
||||
}
|
||||
|
||||
public C randomValue(final ExtendedRandom random) {
|
||||
return this.random.apply(random);
|
||||
}
|
||||
}
|
||||
7
java/expression/common/package-info.java
Normal file
7
java/expression/common/package-info.java
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Expressions generators for expression-based homeworks
|
||||
* of <a href="https://www.kgeorgiy.info/courses/paradigms/">Paradigms of Programming</a> course.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
package expression.common;
|
||||
29
java/expression/exceptions/CheckedAdd.java
Normal file
29
java/expression/exceptions/CheckedAdd.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package expression.exceptions;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
|
||||
import expression.*;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class CheckedAdd extends AbstractBinaryOperation {
|
||||
public CheckedAdd(AbstractExpression l, AbstractExpression r) { super(l, r); }
|
||||
|
||||
@Override protected String getOperator() { return "+"; }
|
||||
@Override protected int getPriority() { return 1; }
|
||||
@Override protected boolean isRightAssoc() { return false; }
|
||||
|
||||
@Override
|
||||
protected int applyInt(int a, int b) {
|
||||
int result = a + b;
|
||||
if (((a ^ result) & (b ^ result)) < 0) {
|
||||
throw new OverflowException("addition");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override protected BigInteger applyBi(BigInteger a, BigInteger b) { return a.add(b); }
|
||||
@Override protected BigDecimal applyBd(BigDecimal a, BigDecimal b) { return a.add(b); }
|
||||
}
|
||||
37
java/expression/exceptions/CheckedDivide.java
Normal file
37
java/expression/exceptions/CheckedDivide.java
Normal file
@@ -0,0 +1,37 @@
|
||||
package expression.exceptions;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.math.MathContext;
|
||||
|
||||
import expression.*;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class CheckedDivide extends AbstractBinaryOperation {
|
||||
public CheckedDivide(AbstractExpression l, AbstractExpression r) { super(l, r); }
|
||||
|
||||
@Override protected String getOperator() { return "/"; }
|
||||
@Override protected int getPriority() { return 2; }
|
||||
@Override protected boolean isRightAssoc() { return true; }
|
||||
|
||||
@Override
|
||||
protected int applyInt(int a, int b) {
|
||||
if (b == 0) throw new DivisionByZeroException();
|
||||
if (a == Integer.MIN_VALUE && b == -1) throw new OverflowException("division");
|
||||
return a / b;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BigInteger applyBi(BigInteger a, BigInteger b) {
|
||||
if (b.signum() == 0) throw new DivisionByZeroException();
|
||||
return a.divide(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BigDecimal applyBd(BigDecimal a, BigDecimal b) {
|
||||
if (b.signum() == 0) throw new DivisionByZeroException();
|
||||
return a.divide(b, MathContext.DECIMAL128);
|
||||
}
|
||||
}
|
||||
32
java/expression/exceptions/CheckedMultiply.java
Normal file
32
java/expression/exceptions/CheckedMultiply.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package expression.exceptions;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
|
||||
import expression.*;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class CheckedMultiply extends AbstractBinaryOperation {
|
||||
public CheckedMultiply(AbstractExpression l, AbstractExpression r) { super(l, r); }
|
||||
|
||||
@Override protected String getOperator() { return "*"; }
|
||||
@Override protected int getPriority() { return 2; }
|
||||
@Override protected boolean isRightAssoc() { return false; }
|
||||
|
||||
@Override
|
||||
protected int applyInt(int a, int b) {
|
||||
if (a == 0 || b == 0) return 0;
|
||||
if (a == Integer.MIN_VALUE && b == -1) throw new OverflowException("multiplication");
|
||||
if (b == Integer.MIN_VALUE && a == -1) throw new OverflowException("multiplication");
|
||||
int result = a * b;
|
||||
if (result / a != b) {
|
||||
throw new OverflowException("multiplication");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override protected BigInteger applyBi(BigInteger a, BigInteger b) { return a.multiply(b); }
|
||||
@Override protected BigDecimal applyBd(BigDecimal a, BigDecimal b) { return a.multiply(b); }
|
||||
}
|
||||
44
java/expression/exceptions/CheckedNegate.java
Normal file
44
java/expression/exceptions/CheckedNegate.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package expression.exceptions;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
import expression.*;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class CheckedNegate extends AbstractExpression {
|
||||
private final AbstractExpression operand;
|
||||
|
||||
public CheckedNegate(AbstractExpression operand) {
|
||||
this.operand = operand;
|
||||
}
|
||||
|
||||
private static int checkedNegate(int n) {
|
||||
if (n == Integer.MIN_VALUE) throw new OverflowException("negation");
|
||||
return -n;
|
||||
}
|
||||
|
||||
@Override public int evaluate(int x) { return checkedNegate(operand.evaluate(x)); }
|
||||
@Override public int evaluate(int x, int y, int z) { return checkedNegate(operand.evaluate(x,y,z)); }
|
||||
@Override public int evaluate(List<Integer> vars) { return checkedNegate(operand.evaluate(vars)); }
|
||||
@Override public BigInteger evaluateBi(List<BigInteger> vars) { return operand.evaluateBi(vars).negate(); }
|
||||
@Override public BigDecimal evaluateBd(List<BigDecimal> vars) { return operand.evaluateBd(vars).negate(); }
|
||||
|
||||
@Override public String toString() { return "-(" + operand + ")"; }
|
||||
@Override public String toMiniString() {
|
||||
if (operand instanceof AbstractBinaryOperation) return "-(" + operand.toMiniString() + ")";
|
||||
return "- " + operand.toMiniString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof CheckedNegate)) return false;
|
||||
return operand.equals(((CheckedNegate) obj).operand);
|
||||
}
|
||||
|
||||
@Override public int hashCode() { return -operand.hashCode(); }
|
||||
}
|
||||
31
java/expression/exceptions/CheckedSubtract.java
Normal file
31
java/expression/exceptions/CheckedSubtract.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package expression.exceptions;
|
||||
|
||||
import expression.AbstractBinaryOperation;
|
||||
import expression.AbstractExpression;
|
||||
import expression.OverflowException;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class CheckedSubtract extends AbstractBinaryOperation {
|
||||
public CheckedSubtract(AbstractExpression l, AbstractExpression r) { super(l, r); }
|
||||
|
||||
@Override protected String getOperator() { return "-"; }
|
||||
@Override protected int getPriority() { return 1; }
|
||||
@Override protected boolean isRightAssoc() { return true; }
|
||||
|
||||
@Override
|
||||
protected int applyInt(int a, int b) {
|
||||
int result = a - b;
|
||||
if (((a ^ b) & (a ^ result)) < 0) {
|
||||
throw new OverflowException("subtraction");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override protected BigInteger applyBi(BigInteger a, BigInteger b) { return a.subtract(b); }
|
||||
@Override protected BigDecimal applyBd(BigDecimal a, BigDecimal b) { return a.subtract(b); }
|
||||
}
|
||||
30
java/expression/exceptions/ExceptionsTest.java
Normal file
30
java/expression/exceptions/ExceptionsTest.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package expression.exceptions;
|
||||
|
||||
import base.Selector;
|
||||
import expression.ListExpression;
|
||||
import expression.parser.Operations;
|
||||
|
||||
import static expression.parser.Operations.*;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class ExceptionsTest {
|
||||
private static final ExpressionParser PARSER = new ExpressionParser();
|
||||
private static final Operations.Operation LIST = kind(ListExpression.KIND, PARSER::parse);
|
||||
|
||||
public static final Selector SELECTOR = Selector.composite(ExceptionsTest.class, ExceptionsTester::new, "easy", "hard")
|
||||
.variant("Base", LIST, ADD, SUBTRACT, MULTIPLY, DIVIDE, NEGATE)
|
||||
.variant("3637", ABS, CUBE, CBRT)
|
||||
.variant("3839", ABS, CUBE, CBRT, SQUARE, SQRT)
|
||||
.variant("3435", ABS, SQRT)
|
||||
.variant("3233", ABS, CBRT)
|
||||
.selector();
|
||||
|
||||
private ExceptionsTest() {
|
||||
}
|
||||
|
||||
public static void main(final String... args) {
|
||||
SELECTOR.main(args);
|
||||
}
|
||||
}
|
||||
162
java/expression/exceptions/ExceptionsTestSet.java
Normal file
162
java/expression/exceptions/ExceptionsTestSet.java
Normal file
@@ -0,0 +1,162 @@
|
||||
package expression.exceptions;
|
||||
|
||||
import base.Functional;
|
||||
import base.Named;
|
||||
import base.Pair;
|
||||
import expression.ToMiniString;
|
||||
import expression.Variable;
|
||||
import expression.common.ExpressionKind;
|
||||
import expression.parser.ParserTestSet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.LongBinaryOperator;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class ExceptionsTestSet<E extends ToMiniString, C> extends ParserTestSet<E, C> {
|
||||
private static final int D = 5;
|
||||
private static final List<Integer> OVERFLOW_VALUES = new ArrayList<>();
|
||||
private final char[] CHARS = "AZ+-*%()[]<>".toCharArray();
|
||||
|
||||
static {
|
||||
Functional.addRange(OVERFLOW_VALUES, D, Integer.MIN_VALUE + D);
|
||||
Functional.addRange(OVERFLOW_VALUES, D, Integer.MIN_VALUE / 2);
|
||||
Functional.addRange(OVERFLOW_VALUES, D, (int) -Math.sqrt(Integer.MAX_VALUE));
|
||||
Functional.addRange(OVERFLOW_VALUES, D, 0);
|
||||
Functional.addRange(OVERFLOW_VALUES, D, (int) Math.sqrt(Integer.MAX_VALUE));
|
||||
Functional.addRange(OVERFLOW_VALUES, D, Integer.MAX_VALUE / 2);
|
||||
Functional.addRange(OVERFLOW_VALUES, D, Integer.MAX_VALUE - D);
|
||||
}
|
||||
|
||||
private final List<Named<String>> parsingTest;
|
||||
|
||||
public ExceptionsTestSet(final ExceptionsTester tester, final ParsedKind<E, C> kind) {
|
||||
super(tester, kind, false);
|
||||
parsingTest = tester.parsingTest;
|
||||
}
|
||||
|
||||
private void testParsingErrors() {
|
||||
counter.testForEach(parsingTest, op -> {
|
||||
final List<String> names = Functional.map(kind.kind().variables().generate(counter.random(), 3), Pair::first);
|
||||
final String expr = mangle(op.value(), names);
|
||||
try {
|
||||
kind.parse(expr, names);
|
||||
counter.fail("Successfully parsed '%s'", op.value());
|
||||
} catch (final Exception e) {
|
||||
counter.format("%-30s %s%n", op.name(), e.getClass().getSimpleName() + ": " + e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void testOverflow() {
|
||||
final List<Pair<String, E>> variables = kind.kind().variables().generate(counter.random(), 3);
|
||||
final List<String> names = Functional.map(variables, Pair::first);
|
||||
final Variable vx = (Variable) variables.get(0).second();
|
||||
final Variable vy = (Variable) variables.get(1).second();
|
||||
|
||||
//noinspection Convert2MethodRef
|
||||
testOverflow(names, (a, b) -> a + b, "+", new CheckedAdd(vx, vy));
|
||||
testOverflow(names, (a, b) -> a - b, "-", new CheckedSubtract(vx, vy));
|
||||
testOverflow(names, (a, b) -> a * b, "*", new CheckedMultiply(vx, vy));
|
||||
testOverflow(names, (a, b) -> b == 0 ? Long.MAX_VALUE : a / b, "/", new CheckedDivide(vx, vy));
|
||||
testOverflow(names, (a, b) -> -b, "<- ignore first argument, unary -", new CheckedNegate(vy));
|
||||
}
|
||||
|
||||
private void testOverflow(final List<String> names, final LongBinaryOperator f, final String op, final Object expression) {
|
||||
final ExpressionKind<E, C> kind = this.kind.kind();
|
||||
for (final int a : OVERFLOW_VALUES) {
|
||||
for (final int b : OVERFLOW_VALUES) {
|
||||
final long expected = f.applyAsLong(a, b);
|
||||
final boolean isInt = Integer.MIN_VALUE <= expected && expected <= Integer.MAX_VALUE;
|
||||
try {
|
||||
final C actual = kind.evaluate(
|
||||
kind.cast(expression),
|
||||
names,
|
||||
kind.fromInts(List.of(a, b, 0))
|
||||
);
|
||||
counter.checkTrue(
|
||||
isInt && kind.fromInt((int) expected).equals(actual),
|
||||
"%d %s %d == %d", a, op, b, actual
|
||||
);
|
||||
} catch (final Exception e) {
|
||||
if (isInt) {
|
||||
counter.fail(e, "Unexpected error in %d %s %d", a, op, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void test() {
|
||||
counter.scope("Overflow tests", (Runnable) this::testOverflow);
|
||||
super.test();
|
||||
counter.scope("Parsing error tests", this::testParsingErrors);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected E parse(final String expression, final List<String> variables, final boolean reparse) {
|
||||
final String expr = expression.strip();
|
||||
if (expr.length() > 10) {
|
||||
for (final char ch : CHARS) {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
final int index = 1 + tester.random().nextInt(expr.length() - 2);
|
||||
int pi = index - 1;
|
||||
while (Character.isWhitespace(expr.charAt(pi))) {
|
||||
pi--;
|
||||
}
|
||||
int ni = index;
|
||||
while (Character.isWhitespace(expr.charAt(ni))) {
|
||||
ni++;
|
||||
}
|
||||
final char pc = expr.charAt(pi);
|
||||
final char nc = expr.charAt(ni);
|
||||
if (
|
||||
"-([{*∛√²³₂₃!‖⎵⎴⌊⌈=?".indexOf(nc) < 0 &&
|
||||
(!Character.isLetterOrDigit(pc) || !Character.isLetterOrDigit(ch)) &&
|
||||
nc != ch && pc != ch &&
|
||||
!Character.isLetterOrDigit(nc) && nc != '$'
|
||||
) {
|
||||
shouldFail(
|
||||
variables,
|
||||
"Parsing error expected for " + insert(expr, index, "<ERROR_INSERTED -->" + ch + "<-- ERROR_INSERTED>"),
|
||||
insert(expr, index, String.valueOf(ch))
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
parens(variables, expr, '[', ']');
|
||||
parens(variables, expr, '{', '}');
|
||||
}
|
||||
|
||||
return counter.testV(() -> counter.call("parse", () -> kind.parse(expr, variables)));
|
||||
}
|
||||
|
||||
private static String insert(final String expr, final int index, final String value) {
|
||||
return expr.substring(0, index) + value + expr.substring(index);
|
||||
}
|
||||
|
||||
private void parens(final List<String> variables, final String expr, final char open, final char close) {
|
||||
if (expr.indexOf(open) >= 0) {
|
||||
replaces(variables, expr, open, '(');
|
||||
replaces(variables, expr, close, ')');
|
||||
if (expr.indexOf('(') >= 0) {
|
||||
replaces(variables, expr, '(', open);
|
||||
replaces(variables, expr, ')', close);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void replaces(final List<String> variables, final String expr, final char what, final char by) {
|
||||
final String input = expr.replace(what, by);
|
||||
shouldFail(variables, "Unmatched parentheses: " + input, input);
|
||||
}
|
||||
|
||||
private void shouldFail(final List<String> variables, final String message, final String input) {
|
||||
counter.shouldFail(message, () -> kind.parse(input, variables));
|
||||
}
|
||||
}
|
||||
100
java/expression/exceptions/ExceptionsTester.java
Normal file
100
java/expression/exceptions/ExceptionsTester.java
Normal file
@@ -0,0 +1,100 @@
|
||||
package expression.exceptions;
|
||||
|
||||
import base.Named;
|
||||
import base.TestCounter;
|
||||
import expression.common.Reason;
|
||||
import expression.parser.ParserTestSet;
|
||||
import expression.parser.ParserTester;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.LongBinaryOperator;
|
||||
import java.util.function.LongToIntFunction;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class ExceptionsTester extends ParserTester {
|
||||
/* package-private */ final List<Named<String>> parsingTest = new ArrayList<>(List.of(
|
||||
Named.of("No first argument", "* $y * $z"),
|
||||
Named.of("No middle argument", "$x * * $z"),
|
||||
Named.of("No last argument", "$x * $y * "),
|
||||
Named.of("No first argument'", "1 + (* $y * $z) + 2"),
|
||||
Named.of("No middle argument'", "1 + ($x * / 9) + 3"),
|
||||
Named.of("No last argument'", "1 + ($x * $y - ) + 3"),
|
||||
Named.of("No opening parenthesis", "$x * $y)"),
|
||||
Named.of("No closing parenthesis", "($x * $y"),
|
||||
Named.of("Mismatched closing parenthesis", "($x * $y]"),
|
||||
Named.of("Mismatched open parenthesis", "[$x * $y)"),
|
||||
Named.of("Start symbol", "@$x * $y"),
|
||||
Named.of("Middle symbol", "$x @ * $y"),
|
||||
Named.of("End symbol", "$x * $y@"),
|
||||
Named.of("Constant overflow 1", Integer.MIN_VALUE - 1L + ""),
|
||||
Named.of("Constant overflow 2", Integer.MAX_VALUE + 1L + ""),
|
||||
Named.of("Bare +", "+"),
|
||||
Named.of("Bare -", "-"),
|
||||
Named.of("Bare a", "a"),
|
||||
Named.of("(())", "(())"),
|
||||
Named.of("Spaces in numbers", "10 20")
|
||||
));
|
||||
|
||||
public ExceptionsTester(final TestCounter counter) {
|
||||
super(counter);
|
||||
}
|
||||
|
||||
|
||||
private void parsingTests(final String... tests) {
|
||||
for (final String test : tests) {
|
||||
parsingTest.add(Named.of(test, test));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unary(final String name, final int priority, final BiFunction<Long, LongToIntFunction, Long> op) {
|
||||
if (allowed(name)) {
|
||||
parsingTests(name, "1 * " + name, name + " * 1");
|
||||
}
|
||||
parsingTests(name + "()", name + "(1, 2)");
|
||||
if (name.length() > 1) {
|
||||
parsingTests(name + "q");
|
||||
}
|
||||
if (allLetterAndDigit(name)) {
|
||||
parsingTests(name + "1", name + "q");
|
||||
}
|
||||
super.unary(name, priority, op);
|
||||
}
|
||||
|
||||
private static boolean allowed(final String name) {
|
||||
return !"xyz".contains(name.substring(0, 1)) && !"xyz".contains(name.substring(name.length() - 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binary(final String name, final int priority, final LongBinaryOperator op) {
|
||||
if (allowed(name)) {
|
||||
parsingTests(name);
|
||||
}
|
||||
parsingTests("1 " + name, "1 " + name + " * 3");
|
||||
if (!"-".equals(name)) {
|
||||
parsingTests(name + " 1", "1 * " + name + " 2");
|
||||
}
|
||||
if (allLetterAndDigit(name)) {
|
||||
parsingTests("5" + name + "5", "5 " + name + "5", "5 " + name + "5 5", "1" + name + "x 1", "1 " + name + "x 1");
|
||||
}
|
||||
super.binary(name, priority, op);
|
||||
}
|
||||
|
||||
private static boolean allLetterAndDigit(final String name) {
|
||||
return name.chars().allMatch(Character::isLetterOrDigit);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void test(final ParserTestSet.ParsedKind<?, ?> kind) {
|
||||
new ExceptionsTestSet<>(this, kind).test();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int cast(final long value) {
|
||||
return Reason.overflow(value);
|
||||
}
|
||||
}
|
||||
215
java/expression/exceptions/ExpressionParser.java
Normal file
215
java/expression/exceptions/ExpressionParser.java
Normal file
@@ -0,0 +1,215 @@
|
||||
package expression.exceptions;
|
||||
|
||||
import expression.*;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class ExpressionParser implements ListParser {
|
||||
|
||||
private String src;
|
||||
private int pos;
|
||||
private java.util.List<String> variables;
|
||||
|
||||
@Override
|
||||
public ListExpression parse(String expression, java.util.List<String> variables) {
|
||||
// System.err.println("PARSE: [" + expression + "]");
|
||||
this.src = expression;
|
||||
this.pos = 0;
|
||||
this.variables = variables;
|
||||
AbstractExpression result = parseMinMax();
|
||||
skipWhitespace();
|
||||
if (pos < src.length()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Unexpected character '" + src.charAt(pos) + "' at position " + pos);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private AbstractExpression parseMinMax() {
|
||||
AbstractExpression left = parseAddSub();
|
||||
while (true) {
|
||||
skipWhitespace();
|
||||
if (tryConsume("min")) left = new Min(left, parseAddSub());
|
||||
else if (tryConsume("max")) left = new Max(left, parseAddSub());
|
||||
else if (tryConsume("set")) left = new SetBit(left, parseAddSub());
|
||||
else if (tryConsume("clear")) left = new Clear(left, parseAddSub());
|
||||
else break;
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
private AbstractExpression parseAddSub() {
|
||||
AbstractExpression left = parseMulDiv();
|
||||
while (true) {
|
||||
skipWhitespace();
|
||||
if (pos < src.length() && src.charAt(pos) == '+') {
|
||||
pos++;
|
||||
left = new CheckedAdd(left, parseMulDiv());
|
||||
} else if (pos < src.length() && src.charAt(pos) == '-') {
|
||||
pos++;
|
||||
left = new CheckedSubtract(left, parseMulDiv());
|
||||
} else break;
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
private AbstractExpression parseMulDiv() {
|
||||
AbstractExpression left = parsePower();
|
||||
while (true) {
|
||||
skipWhitespace();
|
||||
if (pos < src.length() && src.charAt(pos) == '*' && nextCharIs('*')) {
|
||||
pos++;
|
||||
left = new CheckedMultiply(left, parsePower());
|
||||
} else if (pos < src.length() && src.charAt(pos) == '/' && nextCharIs('/')) {
|
||||
pos++;
|
||||
left = new CheckedDivide(left, parsePower());
|
||||
} else break;
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
private AbstractExpression parsePower() {
|
||||
AbstractExpression left = parseUnary();
|
||||
while (true) {
|
||||
skipWhitespace();
|
||||
if (pos + 1 < src.length() && src.charAt(pos) == '*' && src.charAt(pos + 1) == '*') {
|
||||
pos += 2;
|
||||
left = new Power(left, parseUnary());
|
||||
} else if (pos + 1 < src.length() && src.charAt(pos) == '/' && src.charAt(pos + 1) == '/') {
|
||||
pos += 2;
|
||||
left = new Log(left, parseUnary());
|
||||
} else break;
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
private AbstractExpression parseUnary() {
|
||||
skipWhitespace();
|
||||
if (pos >= src.length())
|
||||
throw new IllegalArgumentException("Unexpected end of expression at position " + pos);
|
||||
|
||||
if (src.charAt(pos) == '-') {
|
||||
pos++;
|
||||
if (pos < src.length() && Character.isDigit(src.charAt(pos))) return parsePostfix(parseNumber(true));
|
||||
return new CheckedNegate(parseUnary());
|
||||
}
|
||||
|
||||
// abs: ||x|| (U+2016 DOUBLE VERTICAL LINE)
|
||||
if (src.charAt(pos) == '‖') {
|
||||
pos++;
|
||||
AbstractExpression inner = parseMinMax();
|
||||
skipWhitespace();
|
||||
if (pos >= src.length() || src.charAt(pos) != '‖')
|
||||
throw new IllegalArgumentException("Expected ‖ at position " + pos);
|
||||
pos++;
|
||||
return parsePostfix(new Abs(inner));
|
||||
}
|
||||
|
||||
// prefix unary: sqrt U+221A, cbrt U+221B
|
||||
if (src.charAt(pos) == '√') { pos++; return parsePostfix(new Sqrt(parseUnary())); }
|
||||
if (src.charAt(pos) == '∛') { pos++; return parsePostfix(new Cbrt(parseUnary())); }
|
||||
|
||||
if (tryConsume("reverse")) return parsePostfix(new Reverse(parseUnary()));
|
||||
if (tryConsume("digits")) return parsePostfix(new Digits(parseUnary()));
|
||||
if (tryConsume("floor")) return parsePostfix(new Floor(parseUnary()));
|
||||
if (tryConsume("ceiling")) return parsePostfix(new Ceiling(parseUnary()));
|
||||
if (tryConsume("log₂") || tryConsume("log2")) return parsePostfix(new Log2(parseUnary()));
|
||||
if (tryConsume("pow₂") || tryConsume("pow2")) return parsePostfix(new Pow2(parseUnary()));
|
||||
if (tryConsume("low")) return parsePostfix(new Low(parseUnary()));
|
||||
if (tryConsume("high")) return parsePostfix(new High(parseUnary()));
|
||||
|
||||
return parsePostfix(parsePrimary());
|
||||
}
|
||||
|
||||
private AbstractExpression parsePostfix(AbstractExpression base) {
|
||||
skipWhitespace();
|
||||
if (pos < src.length() && src.charAt(pos) == '²') { pos++; return parsePostfix(new Square(base)); }
|
||||
if (pos < src.length() && src.charAt(pos) == '³') { pos++; return parsePostfix(new Cube(base)); }
|
||||
return base;
|
||||
}
|
||||
|
||||
private AbstractExpression parsePrimary() {
|
||||
skipWhitespace();
|
||||
if (pos >= src.length()) throw new IllegalArgumentException("Unexpected end of expression");
|
||||
char c = src.charAt(pos);
|
||||
|
||||
if (c == '(') {
|
||||
pos++;
|
||||
AbstractExpression inner = parseMinMax();
|
||||
skipWhitespace();
|
||||
expect();
|
||||
return inner;
|
||||
}
|
||||
if (c == '$') { pos++; return new Variable(parseIndex()); }
|
||||
if (Character.isDigit(c)) return parseNumber(false);
|
||||
|
||||
if (Character.isLetter(c)) {
|
||||
int start = pos;
|
||||
while (pos < src.length() && (Character.isLetterOrDigit(src.charAt(pos)) || src.charAt(pos) == '_'))
|
||||
pos++;
|
||||
String name = src.substring(start, pos);
|
||||
int idx = variables != null ? variables.indexOf(name) : -1;
|
||||
if (idx >= 0) return new Variable(idx, name);
|
||||
throw new IllegalArgumentException("Unknown identifier '" + name + "' at position " + start);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unexpected character '" + c + "' at position " + pos);
|
||||
}
|
||||
|
||||
private void skipWhitespace() {
|
||||
while (pos < src.length() && Character.isWhitespace(src.charAt(pos))) pos++;
|
||||
}
|
||||
|
||||
private boolean nextCharIs(char next) {
|
||||
return pos + 1 >= src.length() || src.charAt(pos + 1) != next;
|
||||
}
|
||||
|
||||
private boolean tryConsume(String keyword) {
|
||||
skipWhitespace();
|
||||
if (!src.startsWith(keyword, pos)) return false;
|
||||
int end = pos + keyword.length();
|
||||
if (end < src.length()) {
|
||||
char next = src.charAt(end);
|
||||
if (Character.isLetterOrDigit(next) || next == '_') return false;
|
||||
}
|
||||
if (variables != null && variables.contains(keyword)) return false;
|
||||
pos = end;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void expect() {
|
||||
if (pos >= src.length() || src.charAt(pos) != ')')
|
||||
throw new IllegalArgumentException("Expected '" + ')' + "' at position " + pos
|
||||
+ (pos < src.length() ? ", got '" + src.charAt(pos) + "'" : ", got end of input"));
|
||||
pos++;
|
||||
}
|
||||
|
||||
private AbstractExpression parseNumber(boolean negative) {
|
||||
int start = pos;
|
||||
while (pos < src.length() && Character.isDigit(src.charAt(pos))) pos++;
|
||||
if (start == pos) throw new IllegalArgumentException("Expected digit at position " + pos);
|
||||
String numStr = src.substring(start, pos);
|
||||
int result = 0;
|
||||
for (int i = 0; i < numStr.length(); i++) {
|
||||
int digit = numStr.charAt(i) - '0';
|
||||
if (!negative) {
|
||||
if (result > (Integer.MAX_VALUE - digit) / 10)
|
||||
throw new OverflowException("constant " + numStr);
|
||||
result = result * 10 + digit;
|
||||
} else {
|
||||
if (result < (Integer.MIN_VALUE + digit) / 10)
|
||||
throw new OverflowException("constant -" + numStr);
|
||||
result = result * 10 - digit;
|
||||
}
|
||||
}
|
||||
return new Const(result);
|
||||
}
|
||||
|
||||
private int parseIndex() {
|
||||
int start = pos;
|
||||
while (pos < src.length() && Character.isDigit(src.charAt(pos))) pos++;
|
||||
if (start == pos) throw new IllegalArgumentException("Expected digit after '$' at position " + pos);
|
||||
return Integer.parseInt(src.substring(start, pos));
|
||||
}
|
||||
}
|
||||
13
java/expression/exceptions/ListParser.java
Normal file
13
java/expression/exceptions/ListParser.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package expression.exceptions;
|
||||
|
||||
import expression.ListExpression;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ListParser {
|
||||
ListExpression parse(String expression, final List<String> variables) throws Exception;
|
||||
}
|
||||
8
java/expression/exceptions/package-info.java
Normal file
8
java/expression/exceptions/package-info.java
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Tests for <a href="https://www.kgeorgiy.info/courses/paradigms/homeworks.html#expressions-exceptions">Expression Error Handling</a> homework
|
||||
* of <a href="https://www.kgeorgiy.info/courses/paradigms/">Paradigms of Programming</a> course.
|
||||
*
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
package expression.exceptions;
|
||||
25
java/expression/generic/ArithmeticType.java
Normal file
25
java/expression/generic/ArithmeticType.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package expression.generic;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public interface ArithmeticType<T> {
|
||||
T fromInt(int value);
|
||||
T add(T a, T b);
|
||||
T subtract(T a, T b);
|
||||
T multiply(T a, T b);
|
||||
T divide(T a, T b);
|
||||
T negate(T a);
|
||||
|
||||
default T count(T a) {
|
||||
throw new UnsupportedOperationException("count");
|
||||
}
|
||||
|
||||
default T min(T a, T b) {
|
||||
throw new UnsupportedOperationException("min");
|
||||
}
|
||||
|
||||
default T max(T a, T b) {
|
||||
throw new UnsupportedOperationException("max");
|
||||
}
|
||||
}
|
||||
61
java/expression/generic/BigIntegerType.java
Normal file
61
java/expression/generic/BigIntegerType.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package expression.generic;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class BigIntegerType implements ArithmeticType<BigInteger> {
|
||||
|
||||
public static final BigIntegerType INSTANCE = new BigIntegerType();
|
||||
|
||||
private BigIntegerType() {}
|
||||
|
||||
@Override
|
||||
public BigInteger fromInt(int value) {
|
||||
return BigInteger.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger add(BigInteger a, BigInteger b) {
|
||||
return a.add(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger subtract(BigInteger a, BigInteger b) {
|
||||
return a.subtract(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger multiply(BigInteger a, BigInteger b) {
|
||||
return a.multiply(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger divide(BigInteger a, BigInteger b) {
|
||||
if (b.equals(BigInteger.ZERO)) throw new ArithmeticException(
|
||||
"Division by zero"
|
||||
);
|
||||
return a.divide(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger negate(BigInteger a) {
|
||||
return a.negate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger count(BigInteger a) {
|
||||
return BigInteger.valueOf(a.bitCount());
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger min(BigInteger a, BigInteger b) {
|
||||
return a.min(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger max(BigInteger a, BigInteger b) {
|
||||
return a.max(b);
|
||||
}
|
||||
}
|
||||
56
java/expression/generic/DoubleType.java
Normal file
56
java/expression/generic/DoubleType.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package expression.generic;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class DoubleType implements ArithmeticType<Double> {
|
||||
|
||||
public static final DoubleType INSTANCE = new DoubleType();
|
||||
|
||||
private DoubleType() {}
|
||||
|
||||
@Override
|
||||
public Double fromInt(int value) {
|
||||
return (double) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double add(Double a, Double b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double subtract(Double a, Double b) {
|
||||
return a - b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double multiply(Double a, Double b) {
|
||||
return a * b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double divide(Double a, Double b) {
|
||||
return a / b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double negate(Double a) {
|
||||
return -a;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double count(Double a) {
|
||||
return (double) Long.bitCount(Double.doubleToLongBits(a));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double min(Double a, Double b) {
|
||||
return Math.min(a, b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double max(Double a, Double b) {
|
||||
return Math.max(a, b);
|
||||
}
|
||||
}
|
||||
56
java/expression/generic/FloatType.java
Normal file
56
java/expression/generic/FloatType.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package expression.generic;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class FloatType implements ArithmeticType<Float> {
|
||||
|
||||
public static final FloatType INSTANCE = new FloatType();
|
||||
|
||||
private FloatType() {}
|
||||
|
||||
@Override
|
||||
public Float fromInt(int value) {
|
||||
return (float) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float add(Float a, Float b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float subtract(Float a, Float b) {
|
||||
return a - b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float multiply(Float a, Float b) {
|
||||
return a * b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float divide(Float a, Float b) {
|
||||
return a / b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float negate(Float a) {
|
||||
return -a;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float count(Float a) {
|
||||
return (float) Integer.bitCount(Float.floatToIntBits(a));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float min(Float a, Float b) {
|
||||
return Math.min(a, b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float max(Float a, Float b) {
|
||||
return Math.max(a, b);
|
||||
}
|
||||
}
|
||||
10
java/expression/generic/GenericExpr.java
Normal file
10
java/expression/generic/GenericExpr.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package expression.generic;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public interface GenericExpr<T> {
|
||||
T evaluate(ArithmeticType<T> type, List<T> vars);
|
||||
}
|
||||
239
java/expression/generic/GenericParser.java
Normal file
239
java/expression/generic/GenericParser.java
Normal file
@@ -0,0 +1,239 @@
|
||||
package expression.generic;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class GenericParser {
|
||||
|
||||
private static final List<String> VARIABLES = List.of("x", "y", "z");
|
||||
|
||||
private String src;
|
||||
private int pos;
|
||||
|
||||
public <T> GenericExpr<T> parse(String expression) {
|
||||
this.src = expression;
|
||||
this.pos = 0;
|
||||
GenericExpr<T> result = parseMinMax();
|
||||
skipWhitespace();
|
||||
if (pos < src.length()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Unexpected character '" +
|
||||
src.charAt(pos) +
|
||||
"' at position " +
|
||||
pos
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private <T> GenericExpr<T> parseMinMax() {
|
||||
GenericExpr<T> left = parseAddSub();
|
||||
while (true) {
|
||||
skipWhitespace();
|
||||
if (matchKeyword("min")) {
|
||||
GenericExpr<T> right = parseAddSub();
|
||||
GenericExpr<T> l = left;
|
||||
left = (type, vars) ->
|
||||
type.min(
|
||||
l.evaluate(type, vars),
|
||||
right.evaluate(type, vars)
|
||||
);
|
||||
} else if (matchKeyword("max")) {
|
||||
GenericExpr<T> right = parseAddSub();
|
||||
GenericExpr<T> l = left;
|
||||
left = (type, vars) ->
|
||||
type.max(
|
||||
l.evaluate(type, vars),
|
||||
right.evaluate(type, vars)
|
||||
);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
private <T> GenericExpr<T> parseAddSub() {
|
||||
GenericExpr<T> left = parseMulDiv();
|
||||
while (true) {
|
||||
skipWhitespace();
|
||||
if (pos < src.length() && src.charAt(pos) == '+') {
|
||||
pos++;
|
||||
GenericExpr<T> right = parseMulDiv();
|
||||
GenericExpr<T> l = left;
|
||||
left = (type, vars) ->
|
||||
type.add(
|
||||
l.evaluate(type, vars),
|
||||
right.evaluate(type, vars)
|
||||
);
|
||||
} else if (
|
||||
pos < src.length() && src.charAt(pos) == '-' && !nextIsKeyword()
|
||||
) {
|
||||
pos++;
|
||||
GenericExpr<T> right = parseMulDiv();
|
||||
GenericExpr<T> l = left;
|
||||
left = (type, vars) ->
|
||||
type.subtract(
|
||||
l.evaluate(type, vars),
|
||||
right.evaluate(type, vars)
|
||||
);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
private <T> GenericExpr<T> parseMulDiv() {
|
||||
GenericExpr<T> left = parseUnary();
|
||||
while (true) {
|
||||
skipWhitespace();
|
||||
if (pos < src.length() && src.charAt(pos) == '*') {
|
||||
pos++;
|
||||
GenericExpr<T> right = parseUnary();
|
||||
GenericExpr<T> l = left;
|
||||
left = (type, vars) ->
|
||||
type.multiply(
|
||||
l.evaluate(type, vars),
|
||||
right.evaluate(type, vars)
|
||||
);
|
||||
} else if (pos < src.length() && src.charAt(pos) == '/') {
|
||||
pos++;
|
||||
GenericExpr<T> right = parseUnary();
|
||||
GenericExpr<T> l = left;
|
||||
left = (type, vars) ->
|
||||
type.divide(
|
||||
l.evaluate(type, vars),
|
||||
right.evaluate(type, vars)
|
||||
);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
private <T> GenericExpr<T> parseUnary() {
|
||||
skipWhitespace();
|
||||
if (pos >= src.length()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Unexpected end of expression at position " + pos
|
||||
);
|
||||
}
|
||||
if (src.charAt(pos) == '-') {
|
||||
pos++;
|
||||
skipWhitespace();
|
||||
if (pos < src.length() && Character.isDigit(src.charAt(pos))) {
|
||||
return parseNumber(true);
|
||||
}
|
||||
GenericExpr<T> operand = parseUnary();
|
||||
return (type, vars) -> type.negate(operand.evaluate(type, vars));
|
||||
}
|
||||
if (matchKeyword("count")) {
|
||||
GenericExpr<T> operand = parseUnary();
|
||||
return (type, vars) -> type.count(operand.evaluate(type, vars));
|
||||
}
|
||||
return parsePrimary();
|
||||
}
|
||||
|
||||
private <T> GenericExpr<T> parsePrimary() {
|
||||
skipWhitespace();
|
||||
if (pos >= src.length()) {
|
||||
throw new IllegalArgumentException("Unexpected end of expression");
|
||||
}
|
||||
char c = src.charAt(pos);
|
||||
|
||||
if (c == '(') {
|
||||
pos++;
|
||||
GenericExpr<T> inner = parseMinMax();
|
||||
skipWhitespace();
|
||||
expect();
|
||||
return inner;
|
||||
}
|
||||
|
||||
if (Character.isDigit(c)) {
|
||||
return parseNumber(false);
|
||||
}
|
||||
|
||||
if (Character.isLetter(c)) {
|
||||
int start = pos;
|
||||
while (
|
||||
pos < src.length() && Character.isLetterOrDigit(src.charAt(pos))
|
||||
) {
|
||||
pos++;
|
||||
}
|
||||
String name = src.substring(start, pos);
|
||||
int idx = VARIABLES.indexOf(name);
|
||||
if (idx >= 0) {
|
||||
return (type, vars) -> vars.get(idx);
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"Unknown variable '" + name + "' at position " + start
|
||||
);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
"Unexpected character '" + c + "' at position " + pos
|
||||
);
|
||||
}
|
||||
|
||||
private <T> GenericExpr<T> parseNumber(boolean negative) {
|
||||
int start = pos;
|
||||
while (pos < src.length() && Character.isDigit(src.charAt(pos))) {
|
||||
pos++;
|
||||
}
|
||||
if (start == pos) {
|
||||
throw new IllegalArgumentException(
|
||||
"Expected digit at position " + pos
|
||||
);
|
||||
}
|
||||
String numStr = src.substring(start, pos);
|
||||
long val = Long.parseLong(numStr);
|
||||
if (negative) val = -val;
|
||||
if (val < Integer.MIN_VALUE || val > Integer.MAX_VALUE) {
|
||||
throw new IllegalArgumentException("Integer overflow: " + val);
|
||||
}
|
||||
int intVal = (int) val;
|
||||
return (type, vars) -> type.fromInt(intVal);
|
||||
}
|
||||
|
||||
private boolean matchKeyword(String keyword) {
|
||||
int i = pos;
|
||||
for (char ch : keyword.toCharArray()) {
|
||||
if (i >= src.length() || src.charAt(i) != ch) return false;
|
||||
i++;
|
||||
}
|
||||
if (
|
||||
i < src.length() && Character.isLetterOrDigit(src.charAt(i))
|
||||
) return false;
|
||||
pos = i;
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean nextIsKeyword() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void skipWhitespace() {
|
||||
while (pos < src.length() && Character.isWhitespace(src.charAt(pos))) {
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
private void expect() {
|
||||
if (pos >= src.length() || src.charAt(pos) != ')') {
|
||||
throw new IllegalArgumentException(
|
||||
"Expected '" +
|
||||
')' +
|
||||
"' at position " +
|
||||
pos +
|
||||
(pos < src.length()
|
||||
? ", got '" + src.charAt(pos) + "'"
|
||||
: ", got end of input")
|
||||
);
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
135
java/expression/generic/GenericTabulator.java
Normal file
135
java/expression/generic/GenericTabulator.java
Normal file
@@ -0,0 +1,135 @@
|
||||
package expression.generic;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class GenericTabulator implements Tabulator {
|
||||
|
||||
@Override
|
||||
public Object[][][] tabulate(
|
||||
String mode,
|
||||
String expression,
|
||||
int x1,
|
||||
int x2,
|
||||
int y1,
|
||||
int y2,
|
||||
int z1,
|
||||
int z2
|
||||
) throws Exception {
|
||||
return switch (mode) {
|
||||
case "i" -> compute(
|
||||
IntCheckedType.INSTANCE,
|
||||
expression,
|
||||
x1,
|
||||
x2,
|
||||
y1,
|
||||
y2,
|
||||
z1,
|
||||
z2
|
||||
);
|
||||
case "d" -> compute(
|
||||
DoubleType.INSTANCE,
|
||||
expression,
|
||||
x1,
|
||||
x2,
|
||||
y1,
|
||||
y2,
|
||||
z1,
|
||||
z2
|
||||
);
|
||||
case "bi" -> compute(
|
||||
BigIntegerType.INSTANCE,
|
||||
expression,
|
||||
x1,
|
||||
x2,
|
||||
y1,
|
||||
y2,
|
||||
z1,
|
||||
z2
|
||||
);
|
||||
case "u" -> compute(
|
||||
IntUncheckedType.INSTANCE,
|
||||
expression,
|
||||
x1,
|
||||
x2,
|
||||
y1,
|
||||
y2,
|
||||
z1,
|
||||
z2
|
||||
);
|
||||
case "s" -> compute(
|
||||
ShortType.INSTANCE,
|
||||
expression,
|
||||
x1,
|
||||
x2,
|
||||
y1,
|
||||
y2,
|
||||
z1,
|
||||
z2
|
||||
);
|
||||
case "f" -> compute(
|
||||
FloatType.INSTANCE,
|
||||
expression,
|
||||
x1,
|
||||
x2,
|
||||
y1,
|
||||
y2,
|
||||
z1,
|
||||
z2
|
||||
);
|
||||
case "t", "it" -> compute(
|
||||
IntTruncType.INSTANCE,
|
||||
expression,
|
||||
x1,
|
||||
x2,
|
||||
y1,
|
||||
y2,
|
||||
z1,
|
||||
z2
|
||||
);
|
||||
default -> throw new IllegalArgumentException(
|
||||
"Unknown mode: " + mode
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
private <T> Object[][][] compute(
|
||||
ArithmeticType<T> type,
|
||||
String expression,
|
||||
int x1,
|
||||
int x2,
|
||||
int y1,
|
||||
int y2,
|
||||
int z1,
|
||||
int z2
|
||||
) {
|
||||
GenericExpr<T> expr = new GenericParser().parse(expression);
|
||||
|
||||
int xLen = x2 - x1 + 1;
|
||||
int yLen = y2 - y1 + 1;
|
||||
int zLen = z2 - z1 + 1;
|
||||
Object[][][] result = new Object[xLen][yLen][zLen];
|
||||
|
||||
for (int xi = 0; xi < xLen; xi++) {
|
||||
for (int yi = 0; yi < yLen; yi++) {
|
||||
for (int zi = 0; zi < zLen; zi++) {
|
||||
T x = type.fromInt(x1 + xi);
|
||||
T y = type.fromInt(y1 + yi);
|
||||
T z = type.fromInt(z1 + zi);
|
||||
try {
|
||||
result[xi][yi][zi] = expr.evaluate(
|
||||
type,
|
||||
List.of(x, y, z)
|
||||
);
|
||||
} catch (Exception e) {
|
||||
result[xi][yi][zi] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user