diff --git a/java/expression/common/Expr.java b/java/expression/common/Expr.java new file mode 100644 index 0000000..8ce7841 --- /dev/null +++ b/java/expression/common/Expr.java @@ -0,0 +1,35 @@ +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(Node node, List> variables) { + public List> variables( + final BiFunction f + ) { + return Functional.map(variables, variable -> + variable.second(f.apply(variable.first(), variable.second())) + ); + } + + public Expr convert(final BiFunction f) { + return of(node, variables(f)); + } + + public Expr node(final Function, Node> f) { + return of(f.apply(node), variables); + } + + public static Expr of( + final Node node, + final List> variables + ) { + return new Expr<>(node, variables); + } +} diff --git a/java/expression/common/ExpressionKind.java b/java/expression/common/ExpressionKind.java new file mode 100644 index 0000000..5ed7276 --- /dev/null +++ b/java/expression/common/ExpressionKind.java @@ -0,0 +1,108 @@ +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 { + + private final Type type; + private final Class kind; + private final Variables variables; + private final Evaluator evaluator; + + public ExpressionKind( + final Type type, + final Class kind, + final Variables variables, + final Evaluator evaluator + ) { + this.type = type; + this.kind = kind; + this.variables = variables; + this.evaluator = evaluator; + } + + public ExpressionKind( + final Type type, + final Class kind, + final List> variables, + final Evaluator evaluator + ) { + this(type, kind, (r, c) -> variables, evaluator); + } + + public C evaluate( + final E expression, + final List variables, + final List 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> allValues( + final int length, + final List values + ) { + return Functional.allValues(fromInts(values), length); + } + + public List fromInts(final List 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 withVariables(final Variables variables) { + return new ExpressionKind<>(type, kind, variables, evaluator); + } + + public Variables variables() { + return variables; + } + + @FunctionalInterface + public interface Variables { + List> generate( + final ExtendedRandom random, + final int count + ); + } + + @FunctionalInterface + public interface Evaluator { + R evaluate( + final E expression, + final List vars, + final List values + ) throws Exception; + } +} diff --git a/java/expression/common/Generator.java b/java/expression/common/Generator.java new file mode 100644 index 0000000..ebf0707 --- /dev/null +++ b/java/expression/common/Generator.java @@ -0,0 +1,233 @@ +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 { + + private final Supplier constant; + private final List> ops; + private final ExpressionKind.Variables variables; + private final Set forbidden; + private final ExtendedRandom random; + private final List>, Stream>>> basicTests; + + public Generator( + final Supplier constant, + final List> ops, + final ExpressionKind.Variables variables, + final Set forbidden, + final ExtendedRandom random, + final List>, Stream>>> 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 Builder builder( + final Supplier constant, + final ExtendedRandom random + ) { + return new Builder<>(random, constant); + } + + public void testRandom( + final TestCounter counter, + final int denominator, + final Consumer> 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> consumer, + final int seq, + final int levels, + final int perLevel, + final BiFunction>, Integer, Node> 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> 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 generate( + final List> variables, + final boolean nullary, + final Supplier> unary, + final Supplier, Node>> binary + ) { + if (nullary || ops.isEmpty()) { + return random.nextBoolean() + ? random.randomItem(variables) + : Node.constant(constant.get()); + } else { + final Named 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> pair = binary.get(); + return Node.op(op.name(), pair.first(), pair.second()); + } + } + } + + private Node generate( + final List> variables, + final boolean nullary, + final Supplier> child + ) { + return generate(variables, nullary, child, () -> + Pair.of(child.get(), child.get()) + ); + } + + private Node generateFullDepth( + final List> variables, + final int depth + ) { + return generate(variables, depth == 0, () -> + generateFullDepth(variables, depth - 1) + ); + } + + private Node generatePartialDepth( + final List> variables, + final int depth + ) { + return generate(variables, depth == 0, () -> + generatePartialDepth(variables, random.nextInt(depth)) + ); + } + + private Node generateSize( + final List> 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> consumer) { + basicTests.forEach(test -> { + final List> 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> variables(final int count) { + List> 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 { + + private final ExtendedRandom random; + private final Supplier constant; + + private final List> ops = new ArrayList<>(); + private final Set forbidden = new HashSet<>(); + + private Builder( + final ExtendedRandom random, + final Supplier 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 Generator build( + final ExpressionKind.Variables variables, + final List>, Stream>>> basicTests + ) { + return new Generator<>( + constant, + ops, + variables, + forbidden, + random, + basicTests + ); + } + } +} diff --git a/java/expression/common/Node.java b/java/expression/common/Node.java new file mode 100644 index 0000000..5b062ba --- /dev/null +++ b/java/expression/common/Node.java @@ -0,0 +1,173 @@ +package expression.common; + +import java.util.function.Function; + +/** + * @author Georgiy Korneev (kgeorgiy@kgeorgiy.info) + */ +public abstract class Node { + + private Node() {} + + public abstract R get( + Const con, + Nullary nul, + Unary, R> un, + Binary, R> bin + ); + + public abstract R cata( + Const con, + Nullary nul, + Unary un, + Binary 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 Node constant(final T value) { + return new Node<>() { + @Override + public R get( + final Const con, + final Nullary nul, + final Unary, R> un, + final Binary, R> bin + ) { + return con.apply(value); + } + + @Override + public R cata( + final Const con, + final Nullary nul, + final Unary un, + final Binary bin + ) { + return con.apply(value); + } + }; + } + + public static Node op(final String name) { + return new Node<>() { + @Override + public R get( + final Const con, + final Nullary nul, + final Unary, R> un, + final Binary, R> bin + ) { + return nul.apply(name); + } + + @Override + public R cata( + final Const con, + final Nullary nul, + final Unary un, + final Binary bin + ) { + return nul.apply(name); + } + }; + } + + public static Node op( + final String name, + final int priority, + final Node arg + ) { + return new Node<>() { + @Override + public R get( + final Const con, + final Nullary nul, + final Unary, R> un, + final Binary, R> bin + ) { + return un.apply(name, priority, arg); + } + + @Override + public R cata( + final Const con, + final Nullary nul, + final Unary un, + final Binary bin + ) { + return un.apply(name, priority, arg.cata(con, nul, un, bin)); + } + }; + } + + public static Node op( + final String name, + final Node arg1, + final Node arg2 + ) { + return new Node<>() { + @Override + public R get( + final Const con, + final Nullary nul, + final Unary, R> un, + final Binary, R> bin + ) { + return bin.apply(name, arg1, arg2); + } + + @Override + public R cata( + final Const con, + final Nullary nul, + final Unary un, + final Binary bin + ) { + return bin.apply( + name, + arg1.cata(con, nul, un, bin), + arg2.cata(con, nul, un, bin) + ); + } + }; + } + + @FunctionalInterface + public interface Const extends Function {} + + @FunctionalInterface + public interface Nullary extends Function {} + + @FunctionalInterface + public interface Unary { + R apply(String name, int priority, T arg); + } + + @FunctionalInterface + public interface Binary { + R apply(String name, T arg1, T arg2); + } +} diff --git a/java/expression/common/NodeRenderer.java b/java/expression/common/NodeRenderer.java new file mode 100644 index 0000000..02f2bf1 --- /dev/null +++ b/java/expression/common/NodeRenderer.java @@ -0,0 +1,114 @@ +package expression.common; + +import base.ExtendedRandom; +import java.util.List; +import java.util.Map; + +/** + * @author Georgiy Korneev (kgeorgiy@kgeorgiy.info) + */ +public class NodeRenderer { + + public static final String PAREN = "["; + public static final List 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> renderer; + private final Map brackets; + private final ExtendedRandom random; + + public NodeRenderer( + final Renderer> renderer, + final Map brackets, + final ExtendedRandom random + ) { + this.renderer = renderer; + this.brackets = Map.copyOf(brackets); + this.random = random; + } + + public static Node paren( + final boolean condition, + final Node 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 renderToNode( + final Settings settings, + final Expr expr + ) { + final Expr> convert = expr.convert((name, variable) -> + Node.op(name) + ); + return renderer.render(convert, settings); + } + + public String render(final Node node, final List 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 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 parens) { + public Settings(final Mode mode, final int limit) { + this(mode, limit, DEFAULT_PARENS); + } + + public Node extra(Node node, final ExtendedRandom random) { + while (random.nextInt(Integer.MAX_VALUE) < limit) { + node = paren(true, node); + } + return node; + } + + public Settings withParens(final List parens) { + return this.parens.equals(parens) + ? this + : new Settings(mode, limit, List.copyOf(parens)); + } + } +}