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