package reverse; import base.*; import java.util.*; import java.util.function.*; import java.util.stream.Collectors; import java.util.stream.IntStream; /** * @author Georgiy Korneev (kgeorgiy@kgeorgiy.info) */ public final class ReverseTester { public static final Named TRANSFORM = Named.of("", ReverseTester::transform); public static final Named SPACE = Named.of("", " "); @FunctionalInterface public interface Op extends Function {} private static final int[] DIVISORS = {100, 10, 1}; private final Op transform; private final BiFunction inputToString; private final BiFunction outputToString; private final String name; private final String spaces; private ReverseTester(final String className, final Op transform, final String spaces) { this(className, transform, spaces, (r, i) -> Integer.toString(i), (r, i) -> Long.toString(i)); } private ReverseTester( final String className, final Op transform, final String spaces, final BiFunction inputToString, final BiFunction outputToString ) { name = className; this.transform = transform; this.spaces = spaces; this.inputToString = inputToString; this.outputToString = outputToString; } private static Consumer variant(final int maxSize, final Supplier tester) { return counter -> tester.get().run(counter, maxSize); } public static Consumer variant(final int maxSize, final Named transform) { return variant(maxSize, transform, SPACE); } public static Consumer variant(final int maxSize, final Named transform, final Named spaces) { Objects.requireNonNull(transform); Objects.requireNonNull(spaces); return variant( maxSize, () -> new ReverseTester("Reverse" + transform.name() + spaces.name(), transform.value(), spaces.value()) ); } public static Consumer variant( final int maxSize, final String suffix, final Named> input, final Named> output ) { return variant(maxSize, suffix, TRANSFORM, input, output); } public static Consumer variant( final int maxSize, final String suffix, final Named op, final Named> input, final Named> output ) { return variant(maxSize, suffix, op, input, output, SPACE); } public static Consumer variant( final int maxSize, final String suffix, final Named op, final Named> input, final Named> output, final Named spaces ) { final String out = input.name().contains(output.name()) ? "" : output.name(); return variant(maxSize, () -> new ReverseTester( "Reverse" + op.name() + input.name() + out + suffix + spaces.name(), op.value(), spaces.value(), input.value(), output.value() )); } private void run(final TestCounter counter, final int maxSize) { new Checker(counter, maxSize, Runner.packages("", "reverse").std(name), spaces).test(); } @Override public String toString() { return name; } public static long[][] transform(final int[][] ints) { return IntStream.range(1, ints.length + 1) .mapToObj(i -> ints[ints.length - i]) .map(is -> IntStream.range(1, is.length + 1).mapToLong(i -> is[is.length - i]).toArray()) .toArray(long[][]::new); } /** * @author Georgiy Korneev (kgeorgiy@kgeorgiy.info) */ private class Checker extends BaseChecker { private final int maxSize; private final Runner runner; private final String spaces; private final Set manualTests = new HashSet<>(); Checker(final TestCounter counter, final int maxSize, final Runner runner, final String spaces) { super(counter); this.maxSize = maxSize; this.runner = runner; this.spaces = spaces; } public void manualTest(final int[][] ints) { for (final List permutation : permutations(new ArrayList<>(Arrays.asList(ints)))) { final int[][] input = permutation.toArray(int[][]::new); final String[][] lines = toString(input, inputToString); if (manualTests.add(Arrays.deepToString(lines))) { test(lines, toString(transform.apply(input), outputToString)); } } } public void test(final int[][] ints) { test(toString(ints, inputToString), toString(transform.apply(ints), outputToString)); } public void test(final String[][] input, final String[][] output) { final List inputLines = toLines(input, random().randomString(spaces, 1, 10)); final List outputLines = toLines(output, " "); runner.testEquals(counter, inputLines, outputLines); } private String[][] toString(final int[][] ints, final BiFunction toString) { return Arrays.stream(ints) .map(row -> Arrays.stream(row).mapToObj(i -> toString.apply(random(), i)).toArray(String[]::new)) .toArray(String[][]::new); } private String[][] toString(final long[][] ints, final BiFunction toString) { return Arrays.stream(ints) .map(row -> Arrays.stream(row).mapToObj(i -> toString.apply(random(), (int) i)).toArray(String[]::new)) .toArray(String[][]::new); } private List toLines(final String[][] data, final String delimiter) { if (data.length == 0) { return Collections.singletonList(""); } return Arrays.stream(data) .map(row -> String.join(delimiter, row)) .collect(Collectors.toList()); } public int[][] random(final int[] profile) { final int col = random().nextInt(Arrays.stream(profile).max().orElse(0)); final int row = random().nextInt(profile.length); final int m = random().nextInt(5) - 2; final int[][] ints = Arrays.stream(profile).mapToObj(random().getRandom()::ints).map(IntStream::toArray).toArray(int[][]::new); Arrays.stream(ints).filter(r -> col < r.length).forEach(r -> r[col] = Math.abs(r[col]) / 2 * m); ints[row] = Arrays.stream(ints[row]).map(Math::abs).map(v -> v / 2 * m).toArray(); return ints; } public void test() { manualTest(new int[][]{ {1} }); manualTest(new int[][]{ {1, 2}, {3} }); manualTest(new int[][]{ {1, 2, 3}, {4, 5}, {6} }); manualTest(new int[][]{ {1, 2, 3}, {}, {4, 5}, {6} }); manualTest(new int[][]{ {1, 2, 3}, {-4, -5}, {6} }); manualTest(new int[][]{ {1, -2, 3}, {}, {4, -5}, {6} }); manualTest(new int[][]{ {1, 2, 0}, {1, 0}, {0}, }); manualTest(new int[][]{ {1}, {1, 3}, {1, 2, 3}, }); manualTest(new int[][]{ {-1}, {-1, -2}, {-1, -2, -3}, }); manualTest(new int[][]{ {}, }); manualTest(new int[][]{ {}, {}, {}, }); testRandom(tweakProfile(constProfile(10, 10), new int[][]{})); testRandom(tweakProfile(constProfile(100, 100), new int[][]{})); testRandom(randomProfile(100, maxSize)); testRandom(randomProfile(maxSize / 10, maxSize)); testRandom(randomProfile(maxSize, maxSize)); for (final int d : DIVISORS) { final int size = maxSize / d; testRandom(tweakProfile(constProfile(size / 2, 0), new int[][]{{size / 2, 0}})); testRandom(tweakProfile(randomProfile(size, size / 2), new int[][]{{size / 2, 0}})); testRandom(tweakProfile(constProfile(size / 2, 0), new int[][]{{size / 2, size / 2 - 1}})); testRandom(tweakProfile(constProfile(size / 3, 1), new int[][]{{size / 3, size / 6, size / 3 - 1}})); } } private int[] randomProfile(final int length, final int values) { final int[] profile = new int[length]; for (int i = 0; i < values; i++) { profile[random().nextInt(0, length - 1)]++; } return profile; } private void testRandom(final int[] profile) { test(random(profile)); } private int[] constProfile(final int length, final int value) { final int[] profile = new int[length]; Arrays.fill(profile, value); return profile; } private int[] tweakProfile(final int[] profile, final int[][] mods) { for (final int[] mod : mods) { Arrays.stream(mod).skip(1).forEach(i -> profile[i] = mod[0]); } return profile; } } private static List> permutations(final List elements) { final List> result = new ArrayList<>(); permutations(new ArrayList<>(elements), result, elements.size() - 1); return result; } private static void permutations(final List elements, final List> result, final int n) { if (n == 0) { result.add(List.copyOf(elements)); } else { for (int i = 0; i < n; i++) { permutations(elements, result, n - 1); if (n % 2 == 1) { Collections.swap(elements, i, n); } else { Collections.swap(elements, 0, n); } } permutations(elements, result, n - 1); } } }