From 8b9612d1ecd6c1e83d4769aed9a13edb8ec44226 Mon Sep 17 00:00:00 2001 From: codejava Date: Mon, 13 Apr 2026 10:49:59 +0300 Subject: [PATCH] Upload files to "java/expression" --- java/expression/DivisionByZeroException.java | 10 + java/expression/Expression.java | 285 +++++++++++++ java/expression/ExpressionTest.java | 30 ++ java/expression/ExpressionTester.java | 415 +++++++++++++++++++ java/expression/Floor.java | 75 ++++ 5 files changed, 815 insertions(+) create mode 100644 java/expression/DivisionByZeroException.java create mode 100644 java/expression/Expression.java create mode 100644 java/expression/ExpressionTest.java create mode 100644 java/expression/ExpressionTester.java create mode 100644 java/expression/Floor.java diff --git a/java/expression/DivisionByZeroException.java b/java/expression/DivisionByZeroException.java new file mode 100644 index 0000000..f908ef3 --- /dev/null +++ b/java/expression/DivisionByZeroException.java @@ -0,0 +1,10 @@ +package expression; + +/** + * @author Doschennikov Nikita (me@fymio.us) + */ +public class DivisionByZeroException extends ArithmeticException { + public DivisionByZeroException() { + super("division by zero"); + } +} \ No newline at end of file diff --git a/java/expression/Expression.java b/java/expression/Expression.java new file mode 100644 index 0000000..94bb4ce --- /dev/null +++ b/java/expression/Expression.java @@ -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 TYPE = new Type<>(a -> a, ExtendedRandom::nextInt, int.class); + ExpressionKind 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) + ); + } +} diff --git a/java/expression/ExpressionTest.java b/java/expression/ExpressionTest.java new file mode 100644 index 0000000..990a5cb --- /dev/null +++ b/java/expression/ExpressionTest.java @@ -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 v( + final Function> tester + ) { + return t -> tester.apply(t).test(); + } + + public static void main(final String... args) { + SELECTOR.main(args); + } +} diff --git a/java/expression/ExpressionTester.java b/java/expression/ExpressionTester.java new file mode 100644 index 0000000..6de3e61 --- /dev/null +++ b/java/expression/ExpressionTester.java @@ -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 extends Tester { + + private final List VALUES = IntStream.rangeClosed(-10, 10) + .boxed() + .toList(); + private final ExpressionKind kind; + + private final List basic = new ArrayList<>(); + private final List advanced = new ArrayList<>(); + private final Set used = new HashSet<>(); + private final GeneratorBuilder generator; + + private final List> prev = new ArrayList<>(); + private final Map mappings; + + protected ExpressionTester( + final TestCounter counter, + final ExpressionKind kind, + final Function expectedConstant, + final Binary binary, + final BinaryOperator add, + final BinaryOperator sub, + final BinaryOperator mul, + final BinaryOperator div, + final Map 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 kind, + final Function expectedConstant, + final Binary binary, + final BinaryOperator add, + final BinaryOperator sub, + final BinaryOperator mul, + final BinaryOperator 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 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 variables, + final List 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 variables, + final List values + ) { + try { + return kind.evaluate(expression, variables, values); + } catch (final Exception e) { + return e.getClass().getName(); + } + } + + protected ExpressionTester basic( + final String full, + final String mini, + final E expected, + final E actual + ) { + return basicF(full, mini, expected, vars -> actual); + } + + protected ExpressionTester basicF( + final String full, + final String mini, + final E expected, + final Function, E> actual + ) { + return basic(new Test(full, mini, expected, actual)); + } + + private ExpressionTester basic(final Test test) { + Asserts.assertTrue(test.full, used.add(test.full)); + basic.add(test); + return this; + } + + protected ExpressionTester advanced( + final String full, + final String mini, + final E expected, + final E actual + ) { + return advancedF(full, mini, expected, vars -> actual); + } + + protected ExpressionTester advancedF( + final String full, + final String mini, + final E expected, + final Function, E> actual + ) { + Asserts.assertTrue(full, used.add(full)); + advanced.add(new Test(full, mini, expected, actual)); + return this; + } + + protected static Named variable( + final String name, + final E expected + ) { + return Named.of(name, expected); + } + + @FunctionalInterface + public interface Binary { + E apply(BinaryOperator op, E a, E b); + } + + private final class Test { + + private final String full; + private final String mini; + private final E expected; + private final Function, E> actual; + + private Test( + final String full, + final String mini, + final E expected, + final Function, E> actual + ) { + this.full = full; + this.mini = mini; + this.expected = expected; + this.actual = actual; + } + + private void test() { + final List> variables = kind + .variables() + .generate(random(), 3); + final List 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 names) { + for (int i = 0; i < names.size(); i++) { + string = string.replace("$" + (char) ('x' + i), names.get(i)); + } + for (final Map.Entry mapping : mappings.entrySet()) { + string = string.replace( + mapping.getKey(), + mapping.getValue().toString() + ); + } + return string; + } + } + + private final class GeneratorBuilder { + + private final Generator.Builder generator; + private final NodeRendererBuilder renderer = + new NodeRendererBuilder<>(random()); + private final Renderer.Builder expected; + private final Renderer.Builder actual; + private final Renderer.Builder copy; + private final Binary binary; + + private GeneratorBuilder( + final Function expectedConstant, + final Function actualConstant, + final Binary binary, + final Function 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 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 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 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 renderer = this.renderer.build(); + final Renderer expectedRenderer = this.expected.build(); + final Renderer actualRenderer = this.actual.build(); + final expression.common.Generator 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> variables = expr.variables(); + final List names = Functional.map( + variables, + Pair::first + ); + final List 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); + }); + } + } +} diff --git a/java/expression/Floor.java b/java/expression/Floor.java new file mode 100644 index 0000000..edbb6de --- /dev/null +++ b/java/expression/Floor.java @@ -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 vars) { + return floorInt(operand.evaluate(vars)); + } + + @Override + public BigInteger evaluateBi(List vars) { + throw new UnsupportedOperationException( + "floor not supported for BigInteger" + ); + } + + @Override + public BigDecimal evaluateBd(List 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; + } +}