diff --git a/java/reverse/ReverseTest.java b/java/reverse/ReverseTest.java new file mode 100644 index 0000000..3753f31 --- /dev/null +++ b/java/reverse/ReverseTest.java @@ -0,0 +1,205 @@ +package reverse; + +import base.Named; +import base.Selector; +import base.TestCounter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.IntToLongFunction; +import java.util.function.LongBinaryOperator; +import java.util.stream.IntStream; +import reverse.ReverseTester.Op; + +/** + * Tests for {@code Reverse} homework. + * + * @author Georgiy Korneev (kgeorgiy@kgeorgiy.info) + */ +public final class ReverseTest { + + // === Base + public static final Named REVERSE = Named.of( + "", + ReverseTester::transform + ); + + // === Max + + public static final Named MAX_C = Named.of("MaxC", scan2((a, b) -> b)); + public static final Named MAX = Named.of("Max", scan2(Math::max)); + + private static Op scan2(final LongBinaryOperator reduce) { + return ints -> { + // This code is intentionally obscure + final int length = Arrays.stream(ints) + .mapToInt(r -> r.length) + .max() + .orElse(0); + final long[] cs = new long[length]; + final long[] cc = new long[length + 1]; + Arrays.fill(cs, Integer.MIN_VALUE); + Arrays.fill(cc, Integer.MIN_VALUE); + //noinspection NestedAssignment + final long[][] rows = range(ints.length) + .mapToObj(i -> { + range(ints[i].length).forEachOrdered(j -> + cc[j] = reduce.applyAsLong( + cc[j + 1], + cs[j] = Math.max(cs[j], ints[i][j]) + ) + ); + return Arrays.copyOf(cc, ints[i].length); + }) + .toArray(long[][]::new); + return range(ints.length) + .mapToObj(i -> rows[i]) + .toArray(long[][]::new); + }; + } + + private static IntStream range(final int length) { + return IntStream.iterate(length - 1, i -> i >= 0, i -> i - 1); + } + + // === Rotate + public static final Named ROTATE = Named.of("Rotate", ints -> { + final List rows = new ArrayList<>(List.of(ints)); + return IntStream.range( + 0, + Arrays.stream(ints) + .mapToInt(r -> r.length) + .max() + .orElse(0) + ) + .mapToObj(c -> { + rows.removeIf(r -> r.length <= c); + return range(rows.size()) + .mapToObj(rows::get) + .mapToLong(r -> r[c]) + .toArray(); + }) + .toArray(long[][]::new); + }); + + // === Even + public static final Named EVEN = Named.of("Even", ints -> + ReverseTester.transform( + IntStream.range(0, ints.length) + .mapToObj(i -> + IntStream.range(0, ints[i].length) + .filter(j -> (i + j) % 2 == 0) + .map(j -> ints[i][j]) + ) + .map(IntStream::toArray) + .toArray(int[][]::new) + ) + ); + + // Sum + @FunctionalInterface + interface LongTernaryOperator { + long applyAsLong(long a, long b, long c); + } + + public static final Named SUM = cross( + "Sum", + 0, + Long::sum, + (r, c, v) -> r + c - v + ); + + private static long[][] cross( + final int[][] ints, + final IntToLongFunction map, + final LongBinaryOperator reduce, + final int zero, + final LongTernaryOperator get + ) { + // This code is intentionally obscure + final long[] rt = Arrays.stream(ints) + .map(Arrays::stream) + .mapToLong(row -> row.mapToLong(map).reduce(zero, reduce)) + .toArray(); + final long[] ct = new long[Arrays.stream(ints) + .mapToInt(r -> r.length) + .max() + .orElse(0)]; + Arrays.fill(ct, zero); + Arrays.stream(ints).forEach(r -> + IntStream.range(0, r.length).forEach(i -> + ct[i] = reduce.applyAsLong(ct[i], map.applyAsLong(r[i])) + ) + ); + return IntStream.range(0, ints.length) + .mapToObj(r -> + IntStream.range(0, ints[r].length) + .mapToLong(c -> get.applyAsLong(rt[r], ct[c], ints[r][c])) + .toArray() + ) + .toArray(long[][]::new); + } + + private static Named cross( + final String name, + final int zero, + final LongBinaryOperator reduce, + final LongTernaryOperator get + ) { + return Named.of(name, ints -> cross(ints, n -> n, reduce, zero, get)); + } + + public static final Named AVG = avg( + "Avg", + ints -> cross(ints, n -> n, Long::sum, 0, (r, c, v) -> r + c - v), + ints -> cross(ints, n -> 1, Long::sum, 0, (r1, c1, v1) -> r1 + c1 - 1) + ); + + private static Named avg(final String name, final Op fs, final Op fc) { + return Named.of(name, ints -> + avg(ints, fs.apply(ints), fc.apply(ints)) + ); + } + + private static long[][] avg( + final int[][] ints, + final long[][] as, + final long[][] ac + ) { + return IntStream.range(0, ints.length) + .mapToObj(i -> + IntStream.range(0, ints[i].length) + .mapToLong(j -> as[i][j] / ac[i][j]) + .toArray() + ) + .toArray(long[][]::new); + } + + // === Common + + public static final int MAX_SIZE = 10_000 / TestCounter.DENOMINATOR; + + public static final Selector SELECTOR = selector( + ReverseTest.class, + MAX_SIZE + ); + + private ReverseTest() { + // Utility class + } + + public static Selector selector(final Class owner, final int maxSize) { + return new Selector(owner) + .variant("Base", ReverseTester.variant(maxSize, REVERSE)) + .variant("3637", ReverseTester.variant(maxSize, MAX_C)) + .variant("3839", ReverseTester.variant(maxSize, MAX)) + .variant("3435", ReverseTester.variant(maxSize, ROTATE)) + .variant("3233", ReverseTester.variant(maxSize, EVEN)) + .variant("4142", ReverseTester.variant(maxSize, AVG)) + .variant("4749", ReverseTester.variant(maxSize, SUM)); + } + + public static void main(final String... args) { + SELECTOR.main(args); + } +} diff --git a/java/reverse/ReverseTester.java b/java/reverse/ReverseTester.java new file mode 100644 index 0000000..78ba0cf --- /dev/null +++ b/java/reverse/ReverseTester.java @@ -0,0 +1,370 @@ +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); + } + } +}