Upload files to "java/expression"

This commit is contained in:
2026-04-13 10:49:59 +03:00
parent 110790eb94
commit 8b9612d1ec
5 changed files with 815 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
package expression;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class DivisionByZeroException extends ArithmeticException {
public DivisionByZeroException() {
super("division by zero");
}
}

View 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)
);
}
}

View 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);
}
}

View 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);
});
}
}
}

View 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;
}
}