initial commit
This commit is contained in:
BIN
artifacts/FastReverseTest.jar
Normal file
BIN
artifacts/FastReverseTest.jar
Normal file
Binary file not shown.
BIN
artifacts/Md2HtmlTest.jar
Normal file
BIN
artifacts/Md2HtmlTest.jar
Normal file
Binary file not shown.
BIN
artifacts/ReverseTest.jar
Normal file
BIN
artifacts/ReverseTest.jar
Normal file
Binary file not shown.
BIN
artifacts/SumTest.jar
Normal file
BIN
artifacts/SumTest.jar
Normal file
Binary file not shown.
BIN
artifacts/WordStatTest.jar
Normal file
BIN
artifacts/WordStatTest.jar
Normal file
Binary file not shown.
BIN
artifacts/WsppTest.jar
Normal file
BIN
artifacts/WsppTest.jar
Normal file
Binary file not shown.
550
java/RunMe.java
Normal file
550
java/RunMe.java
Normal file
@@ -0,0 +1,550 @@
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentSkipListSet;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Run this code with provided arguments.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
@SuppressWarnings("all")
|
||||
public final class RunMe {
|
||||
private RunMe() {
|
||||
// Utility class
|
||||
}
|
||||
|
||||
public static void main(final String[] args) {
|
||||
final byte[] password = parseArgs(args);
|
||||
|
||||
flag0(password);
|
||||
System.out.println("The first flag was low-hanging fruit, can you find others?");
|
||||
System.out.println("Try to read, understand and modify code in flagX(...) functions");
|
||||
|
||||
flag1(password);
|
||||
flag2(password);
|
||||
flag3(password);
|
||||
flag4(password);
|
||||
flag5(password);
|
||||
flag6(password);
|
||||
flag7(password);
|
||||
flag8(password);
|
||||
flag9(password);
|
||||
flag10(password);
|
||||
flag12(password);
|
||||
flag13(password);
|
||||
flag14(password);
|
||||
flag15(password);
|
||||
flag16(password);
|
||||
flag17(password);
|
||||
flag18(password);
|
||||
flag19(password);
|
||||
flag20(password);
|
||||
}
|
||||
|
||||
private static void flag0(final byte[] password) {
|
||||
// The result of print(...) function depends only on explicit arguments
|
||||
print(0, 0, password);
|
||||
}
|
||||
|
||||
|
||||
private static void flag1(final byte[] password) {
|
||||
while ("true".length() == 4) {
|
||||
}
|
||||
|
||||
print(1, -5204358702485720348L, password);
|
||||
}
|
||||
|
||||
|
||||
private static void flag2(final byte[] password) {
|
||||
int result = 0;
|
||||
for (int i = 0; i < 300_000; i++) {
|
||||
for (int j = 0; j < 300_000; j++) {
|
||||
for (int k = 0; k < 300_000; k++) {
|
||||
result ^= (i * 7) | (j + k);
|
||||
result ^= result << 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print(2, -3458723408232943523L, password);
|
||||
}
|
||||
|
||||
|
||||
private static void flag3(final byte[] password) {
|
||||
int result = 0;
|
||||
for (int i = 0; i < 2025; i++) {
|
||||
for (int j = 0; j < 2025; j++) {
|
||||
for (int k = 0; k < 2025; k++) {
|
||||
for (int p = 0; p < 12; p++) {
|
||||
result ^= (i * 17) | (j + k * 7) & ~p;
|
||||
result ^= result << 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print(3, result, password);
|
||||
}
|
||||
|
||||
|
||||
private static void flag4(final byte[] password) {
|
||||
final long target = 8504327508437503432L + getInt(password);
|
||||
for (long i = 0; i < Long.MAX_VALUE; i++) {
|
||||
if ((i ^ (i >>> 32)) == target) {
|
||||
print(4, i, password);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* package-private */ static final long PRIME = 2025_2025_07;
|
||||
|
||||
private static void flag5(final byte[] password) {
|
||||
final long n = 1_000_000_000_000_000L + getInt(password);
|
||||
|
||||
long result = 0;
|
||||
for (long i = 0; i < n; i++) {
|
||||
result = (result + i / 3 + i / 5 + i / 7 + i / 2025) % PRIME;
|
||||
}
|
||||
|
||||
print(5, result, password);
|
||||
}
|
||||
|
||||
|
||||
private static void flag6(final byte[] password) {
|
||||
/***
|
||||
\u002a\u002f\u0077\u0068\u0069\u006c\u0065\u0020\u0028\u0022\u0031\u0022
|
||||
\u002e\u006c\u0065\u006e\u0067\u0074\u0068\u0028\u0029\u0020\u003d\u003d
|
||||
\u0020\u0031\u0029\u003b\u0020\u0020\u006c\u006f\u006e\u0067\u0020\u0009
|
||||
\u0020\u0020\u0072\u0065\u0073\u0075\u006c\u0074\u0020\u003d\u0020\u000a
|
||||
\u0035\u0037\u0034\u0038\u0035\u0037\u0030\u0032\u0034\u0038\u0033\u004c
|
||||
\u002b\u0070\u0061\u0073\u0073\u0077\u006f\u0072\u0064\u005b\u0035\u005d
|
||||
\u002b\u0070\u0061\u0073\u0073\u0077\u006f\u0072\u0064\u005b\u0032\u005d
|
||||
\u003b\u002f\u002a
|
||||
***/
|
||||
print(6, result, password);
|
||||
}
|
||||
|
||||
|
||||
private static void flag7(final byte[] password) {
|
||||
// Count the number of occurrences of the most frequent noun at the following page:
|
||||
// https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html
|
||||
|
||||
// The singular form of the most frequent noun
|
||||
final String singular = "";
|
||||
// The plural form of the most frequent noun
|
||||
final String plural = "";
|
||||
// The total number of occurrences
|
||||
final int total = 0;
|
||||
if (total != 0) {
|
||||
print(7, (singular + ":" + plural + ":" + total).hashCode(), password);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void flag8(final byte[] password) {
|
||||
// Count the number of bluish (#5984A1) pixels of this image:
|
||||
// https://dev.java/assets/images/java-affinity-logo-icode-lg.png
|
||||
|
||||
final int number = 0;
|
||||
if (number != 0) {
|
||||
print(8, number, password);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final String PATTERN = "Reading the documentation can be surprisingly helpful!";
|
||||
private static final int SMALL_REPEAT_COUNT = 10_000_000;
|
||||
|
||||
private static void flag9(final byte[] password) {
|
||||
String repeated = "";
|
||||
for (int i = 0; i < SMALL_REPEAT_COUNT; i++) {
|
||||
repeated += PATTERN;
|
||||
}
|
||||
|
||||
print(9, repeated.hashCode(), password);
|
||||
}
|
||||
|
||||
|
||||
private static final long LARGE_REPEAT_SHIFT = 29;
|
||||
private static final long LARGE_REPEAT_COUNT = 1L << LARGE_REPEAT_SHIFT;
|
||||
|
||||
private static void flag10(final byte[] password) {
|
||||
String repeated = "";
|
||||
for (long i = 0; i < LARGE_REPEAT_COUNT; i++) {
|
||||
repeated += PATTERN;
|
||||
}
|
||||
|
||||
print(10, repeated.hashCode(), password);
|
||||
}
|
||||
|
||||
|
||||
private static void flag11(final byte[] password) {
|
||||
print(11, 5823470598324780581L, password);
|
||||
}
|
||||
|
||||
|
||||
private static void flag12(final byte[] password) {
|
||||
final BigInteger year = BigInteger.valueOf(-2025);
|
||||
final BigInteger term = BigInteger.valueOf(PRIME + Math.abs(getInt(password)) % PRIME);
|
||||
|
||||
final long result = Stream.iterate(BigInteger.ZERO, BigInteger.ONE::add)
|
||||
.filter(i -> year.multiply(i).add(term).multiply(i).compareTo(BigInteger.TEN) > 0)
|
||||
.mapToLong(i -> i.longValue() * password[i.intValue() % password.length])
|
||||
.sum();
|
||||
|
||||
print(12, result, password);
|
||||
}
|
||||
|
||||
|
||||
private static final long MAX_DEPTH = 100_000_000L;
|
||||
|
||||
private static void flag13(final byte[] password) {
|
||||
try {
|
||||
flag13(password, 0, 0);
|
||||
} catch (final StackOverflowError e) {
|
||||
System.err.println("Stack overflow :((");
|
||||
}
|
||||
}
|
||||
|
||||
private static void flag13(final byte[] password, final long depth, final long result) {
|
||||
if (depth < MAX_DEPTH) {
|
||||
flag13(password, depth + 1, (result ^ PRIME) | (result << 2) + depth * 17);
|
||||
} else {
|
||||
print(13, result, password);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void flag14(final byte[] password) {
|
||||
final Instant today = Instant.parse("2025-09-09T12:00:00Z");
|
||||
final BigInteger hours = BigInteger.valueOf(Duration.between(Instant.EPOCH, today).toHours() + password[1] + password[3]);
|
||||
|
||||
final long result = Stream.iterate(BigInteger.ZERO, hours::add)
|
||||
.reduce(BigInteger.ZERO, BigInteger::add)
|
||||
.longValue();
|
||||
|
||||
print(14, result, password);
|
||||
}
|
||||
|
||||
private static void flag15(final byte[] password) {
|
||||
// REDACTED
|
||||
}
|
||||
|
||||
private static void flag16(final byte[] password) {
|
||||
byte[] a = {
|
||||
(byte) (password[0] + password[3]),
|
||||
(byte) (password[1] + password[4]),
|
||||
(byte) (password[2] + password[5])
|
||||
};
|
||||
|
||||
for (long i = 1_000_000_000_000_000_000L + getInt(password); i >= 0; i--) {
|
||||
flag16Update(a);
|
||||
}
|
||||
|
||||
print(16, flag16Result(a), password);
|
||||
}
|
||||
|
||||
/* package-private */ static void flag16Update(byte[] a) {
|
||||
a[0] ^= a[1];
|
||||
a[1] -= a[2] | a[0];
|
||||
a[2] *= a[0];
|
||||
}
|
||||
|
||||
/* package-private */ static int flag16Result(byte[] a) {
|
||||
return (a[0] + " " + a[1] + " " + a[2]).hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Original idea by Alexei Shishkin.
|
||||
*/
|
||||
private static void flag17(final byte[] password) {
|
||||
final int n = Math.abs(getInt(password) % 2025) + 2025;
|
||||
print(17, calc17(n), password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write me
|
||||
* <pre>
|
||||
* 0: iconst_0
|
||||
* 1: istore_1
|
||||
* 2: iload_1
|
||||
* 3: iload_1
|
||||
* 4: imul
|
||||
* 5: sipush 2025
|
||||
* 8: idiv
|
||||
* 9: iload_0
|
||||
* 10: isub
|
||||
* 11: ifge 20
|
||||
* 14: iinc 1, 1
|
||||
* 17: goto 2
|
||||
* 20: iload_1
|
||||
* 21: ireturn
|
||||
* </pre>
|
||||
*/
|
||||
private static int calc17(final int n) {
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
private static void flag18(final byte[] password) {
|
||||
final int n = 2025 + getInt(password) % 2025;
|
||||
// Find the number of factors of n! modulo PRIME
|
||||
final int factors = 0;
|
||||
if (factors != 0) {
|
||||
print(18, factors, password);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void flag19(final byte[] password) {
|
||||
// Let n = 2025e25 + abs(getInt(password)).
|
||||
// Consider the sequence of numbers (n + i) ** 2.
|
||||
// Instead of each number, we write the number that is obtained from it by discarding the last 25 digits.
|
||||
// How many of the first numbers of the resulting sequence will form an arithmetic progression?
|
||||
final long result = 0;
|
||||
if (result != 0) {
|
||||
print(19, result, password);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Original idea by Dmitrii Liapin.
|
||||
*/
|
||||
private static void flag20(final byte[] password) {
|
||||
final Collection<Long> longs = new Random(getInt(password)).longs(2025_000)
|
||||
.map(n -> n % 1000)
|
||||
.boxed()
|
||||
.collect(Collectors.toCollection(LinkedList::new));
|
||||
|
||||
// Calculate the number of objects (recursively) accessible by "longs" reference.
|
||||
final int result = 0;
|
||||
|
||||
if (result != 0) {
|
||||
print(20, result, password);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Original idea and implementation Igor Panasyuk.
|
||||
*/
|
||||
private static void flag21(final byte[] password) {
|
||||
record Pair(int x, int y) {
|
||||
}
|
||||
|
||||
final List<Object> items = new ArrayList<>(Arrays.asList(
|
||||
Byte.toUnsignedInt(password[0]),
|
||||
(long) getInt(password),
|
||||
new Pair(password[1], password[2]),
|
||||
"Java SE 21 " + Arrays.toString(password)
|
||||
));
|
||||
|
||||
for (int round = 0; round < 10; round++) {
|
||||
for (final Object item : List.copyOf(items)) {
|
||||
// TODO: complete the switch expression using Java 21 features:
|
||||
// items.add(
|
||||
// case Integer i -> square of i as long
|
||||
// case Long l and l is even -> l ^ 0x21L
|
||||
// case Long l and l is odd -> -l
|
||||
// case Pair(int x, int y) -> x << 8 ^ y
|
||||
// case String s -> s.hashCode()
|
||||
// default -> 0
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
||||
long result = 0;
|
||||
for (final Object item : items) {
|
||||
result = result * 17 + item.toString().hashCode();
|
||||
}
|
||||
|
||||
print(21, result, password);
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
// You may ignore all code below this line.
|
||||
// It is not required to get any of the flags.
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
|
||||
private static void print(final int no, long result, final byte[] password) {
|
||||
System.out.format("flag %d: https://www.kgeorgiy.info/courses/prog-intro/hw1/%s%n", no, flag(result, password));
|
||||
}
|
||||
|
||||
/* package-private */ static String flag(long result, byte[] password) {
|
||||
final byte[] flag = password.clone();
|
||||
for (int i = 0; i < 6; i++) {
|
||||
flag[i] ^= result;
|
||||
result >>>= 8;
|
||||
}
|
||||
|
||||
return flag(flag);
|
||||
}
|
||||
|
||||
/* package-private */ static String flag(final byte[] data) {
|
||||
final MessageDigest messageDigest = RunMe.DIGEST.get();
|
||||
messageDigest.update(SALT);
|
||||
messageDigest.update(data);
|
||||
messageDigest.update(SALT);
|
||||
final byte[] digest = messageDigest.digest();
|
||||
|
||||
return IntStream.range(0, 6)
|
||||
.map(i -> (((digest[i * 2] & 255) << 8) + (digest[i * 2 + 1] & 255)) % KEYWORDS.size())
|
||||
.mapToObj(KEYWORDS::get)
|
||||
.collect(Collectors.joining("-"));
|
||||
}
|
||||
|
||||
/* package-private */ static byte[] parseArgs(final String[] args) {
|
||||
if (args.length != 6) {
|
||||
throw error("Expected 6 command line arguments, found: %d", args.length);
|
||||
}
|
||||
|
||||
final byte[] bytes = new byte[args.length];
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
final Byte value = VALUES.get(args[i].toLowerCase(Locale.US));
|
||||
if (value == null) {
|
||||
throw error("Expected keyword, found: %s", args[i]);
|
||||
}
|
||||
bytes[i] = value;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private static AssertionError error(final String format, final Object... args) {
|
||||
System.err.format(format, args);
|
||||
System.err.println();
|
||||
System.exit(1);
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
/* package-private */ static int getInt(byte[] password) {
|
||||
return IntStream.range(0, password.length)
|
||||
.map(i -> password[i])
|
||||
.reduce((a, b) -> a * KEYWORDS.size() + b)
|
||||
.getAsInt();
|
||||
}
|
||||
|
||||
private static final ThreadLocal<MessageDigest> DIGEST = ThreadLocal.withInitial(() -> {
|
||||
try {
|
||||
return MessageDigest.getInstance("SHA-256");
|
||||
} catch (final NoSuchAlgorithmException e) {
|
||||
throw new AssertionError("Cannot create SHA-256 digest", e);
|
||||
}
|
||||
});
|
||||
|
||||
public static final byte[] SALT = "fathdufimmonJiajFik3JeccafdaihoFrusthys9".getBytes(StandardCharsets.US_ASCII);
|
||||
|
||||
private static final List<String> KEYWORDS = List.of(
|
||||
"abstract",
|
||||
"assert",
|
||||
"boolean",
|
||||
"break",
|
||||
"byte",
|
||||
"case",
|
||||
"catch",
|
||||
"char",
|
||||
"class",
|
||||
"const",
|
||||
"new",
|
||||
"package",
|
||||
"private",
|
||||
"protected",
|
||||
"public",
|
||||
"return",
|
||||
"short",
|
||||
"static",
|
||||
"strictfp",
|
||||
"super",
|
||||
"for",
|
||||
"goto",
|
||||
"if",
|
||||
"implements",
|
||||
"import",
|
||||
"instanceof",
|
||||
"int",
|
||||
"interface",
|
||||
"long",
|
||||
"native",
|
||||
"continue",
|
||||
"default",
|
||||
"do",
|
||||
"double",
|
||||
"else",
|
||||
"enum",
|
||||
"extends",
|
||||
"final",
|
||||
"finally",
|
||||
"float",
|
||||
"switch",
|
||||
"synchronized",
|
||||
"this",
|
||||
"throw",
|
||||
"throws",
|
||||
"transient",
|
||||
"try",
|
||||
"void",
|
||||
"volatile",
|
||||
"while",
|
||||
"record",
|
||||
"Error",
|
||||
"AssertionError",
|
||||
"OutOfMemoryError",
|
||||
"StackOverflowError",
|
||||
"ArrayIndexOutOfBoundsException",
|
||||
"ArrayStoreException",
|
||||
"AutoCloseable",
|
||||
"Character",
|
||||
"CharSequence",
|
||||
"ClassCastException",
|
||||
"Comparable",
|
||||
"Exception",
|
||||
"IllegalArgumentException",
|
||||
"IllegalStateException",
|
||||
"IndexOutOfBoundsException",
|
||||
"Integer",
|
||||
"Iterable",
|
||||
"Math",
|
||||
"Module",
|
||||
"NegativeArraySizeException",
|
||||
"NullPointerException",
|
||||
"Number",
|
||||
"NumberFormatException",
|
||||
"Object",
|
||||
"Override",
|
||||
"RuntimeException",
|
||||
"StrictMath",
|
||||
"String",
|
||||
"StringBuilder",
|
||||
"StringIndexOutOfBoundsException",
|
||||
"SuppressWarnings",
|
||||
"System",
|
||||
"Thread",
|
||||
"Throwable",
|
||||
"ArithmeticException",
|
||||
"ClassLoader",
|
||||
"ClassNotFoundException",
|
||||
"Cloneable",
|
||||
"Deprecated",
|
||||
"FunctionalInterface",
|
||||
"InterruptedException",
|
||||
"Process",
|
||||
"ProcessBuilder",
|
||||
"Runnable",
|
||||
"SafeVarargs",
|
||||
"StackTraceElement",
|
||||
"Runtime",
|
||||
"ThreadLocal",
|
||||
"UnsupportedOperationException"
|
||||
);
|
||||
|
||||
private static final Map<String, Byte> VALUES = IntStream.range(0, KEYWORDS.size())
|
||||
.boxed()
|
||||
.collect(Collectors.toMap(index -> KEYWORDS.get(index).toLowerCase(Locale.US), Integer::byteValue));
|
||||
}
|
||||
84
java/base/Asserts.java
Normal file
84
java/base/Asserts.java
Normal file
@@ -0,0 +1,84 @@
|
||||
package base;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
|
||||
public final class Asserts {
|
||||
static {
|
||||
Locale.setDefault(Locale.US);
|
||||
}
|
||||
|
||||
private Asserts() {
|
||||
}
|
||||
|
||||
public static void assertEquals(final String message, final Object expected, final Object actual) {
|
||||
final String reason = String.format("%s:%n expected `%s`,%n actual `%s`",
|
||||
message, toString(expected), toString(actual));
|
||||
assertTrue(reason, Objects.deepEquals(expected, actual));
|
||||
}
|
||||
|
||||
public static String toString(final Object value) {
|
||||
if (value != null && value.getClass().isArray()) {
|
||||
final String result = Arrays.deepToString(new Object[]{value});
|
||||
return result.substring(1, result.length() - 1);
|
||||
} else {
|
||||
return Objects.toString(value);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> void assertEquals(final String message, final List<T> expected, final List<T> actual) {
|
||||
for (int i = 0; i < Math.min(expected.size(), actual.size()); i++) {
|
||||
assertEquals(message + ":" + (i + 1), expected.get(i), actual.get(i));
|
||||
}
|
||||
assertEquals(message + ": Number of items", expected.size(), actual.size());
|
||||
}
|
||||
|
||||
public static void assertTrue(final String message, final boolean value) {
|
||||
if (!value) {
|
||||
throw error("%s", message);
|
||||
}
|
||||
}
|
||||
|
||||
public static void assertEquals(final String message, final double expected, final double actual, final double precision) {
|
||||
assertTrue(
|
||||
String.format("%s: Expected %.12f, found %.12f", message, expected, actual),
|
||||
isEqual(expected, actual, precision)
|
||||
);
|
||||
}
|
||||
|
||||
public static boolean isEqual(final double expected, final double actual, final double precision) {
|
||||
final double error = Math.abs(actual - expected);
|
||||
return error <= precision
|
||||
|| error <= precision * Math.abs(expected)
|
||||
|| !Double.isFinite(expected)
|
||||
|| Math.abs(expected) > 1e100
|
||||
|| Math.abs(expected) < precision && !Double.isFinite(actual);
|
||||
}
|
||||
|
||||
public static void assertSame(final String message, final Object expected, final Object actual) {
|
||||
assertTrue(String.format("%s: expected same objects: %s and %s", message, expected, actual), expected == actual);
|
||||
}
|
||||
|
||||
public static void checkAssert(final Class<?> c) {
|
||||
if (!c.desiredAssertionStatus()) {
|
||||
throw error("You should enable assertions by running 'java -ea %s'", c.getName());
|
||||
}
|
||||
}
|
||||
|
||||
public static AssertionError error(final String format, final Object... args) {
|
||||
final String message = String.format(format, args);
|
||||
return args.length > 0 && args[args.length - 1] instanceof Throwable
|
||||
? new AssertionError(message, (Throwable) args[args.length - 1])
|
||||
: new AssertionError(message);
|
||||
}
|
||||
|
||||
public static void printStackTrace(final String message) {
|
||||
new Exception(message).printStackTrace(System.out);
|
||||
}
|
||||
}
|
||||
20
java/base/BaseChecker.java
Normal file
20
java/base/BaseChecker.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package base;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public abstract class BaseChecker {
|
||||
protected final TestCounter counter;
|
||||
|
||||
protected BaseChecker(final TestCounter counter) {
|
||||
this.counter = counter;
|
||||
}
|
||||
|
||||
public ExtendedRandom random() {
|
||||
return counter.random();
|
||||
}
|
||||
|
||||
public int mode() {
|
||||
return counter.mode();
|
||||
}
|
||||
}
|
||||
95
java/base/Either.java
Normal file
95
java/base/Either.java
Normal file
@@ -0,0 +1,95 @@
|
||||
package base;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public interface Either<L, R> {
|
||||
<NR> Either<L, NR> mapRight(final Function<? super R, NR> f);
|
||||
<NR> Either<L, NR> flatMapRight(final Function<? super R, ? extends Either<L, NR>> f);
|
||||
<T> T either(Function<? super L, ? extends T> lf, Function<? super R, ? extends T> rf);
|
||||
|
||||
boolean isRight();
|
||||
|
||||
L getLeft();
|
||||
R getRight();
|
||||
|
||||
static <L, R> Either<L, R> right(final R value) {
|
||||
return new Either<>() {
|
||||
@Override
|
||||
public <NR> Either<L, NR> mapRight(final Function<? super R, NR> f) {
|
||||
return right(f.apply(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <NR> Either<L, NR> flatMapRight(final Function<? super R, ? extends Either<L, NR>> f) {
|
||||
return f.apply(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T either(final Function<? super L, ? extends T> lf, final Function<? super R, ? extends T> rf) {
|
||||
return rf.apply(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRight() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public L getLeft() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R getRight() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Right(%s)", value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static <L, R> Either<L, R> left(final L value) {
|
||||
return new Either<>() {
|
||||
@Override
|
||||
public <NR> Either<L, NR> mapRight(final Function<? super R, NR> f) {
|
||||
return left(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <NR> Either<L, NR> flatMapRight(final Function<? super R, ? extends Either<L, NR>> f) {
|
||||
return left(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T either(final Function<? super L, ? extends T> lf, final Function<? super R, ? extends T> rf) {
|
||||
return lf.apply(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRight() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public L getLeft() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R getRight() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Left(%s)", value);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
89
java/base/ExtendedRandom.java
Normal file
89
java/base/ExtendedRandom.java
Normal file
@@ -0,0 +1,89 @@
|
||||
package base;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class ExtendedRandom {
|
||||
public static final String ENGLISH = "abcdefghijklmnopqrstuvwxyz";
|
||||
public static final String RUSSIAN = "абвгдеежзийклмнопрстуфхцчшщъыьэюя";
|
||||
public static final String GREEK = "αβγŋδεζηθικλμνξοπρτυφχψω";
|
||||
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
|
||||
public static final String SPACES = " \t\n\u000B\u2029\f";
|
||||
|
||||
private final Random random;
|
||||
|
||||
public ExtendedRandom(final Random random) {
|
||||
this.random = random;
|
||||
}
|
||||
|
||||
public ExtendedRandom(final Class<?> owner) {
|
||||
this(new Random(7912736473497634913L + owner.getName().hashCode()));
|
||||
}
|
||||
|
||||
public String randomString(final String chars) {
|
||||
return randomChar(chars) + (random.nextBoolean() ? "" : randomString(chars));
|
||||
}
|
||||
|
||||
public char randomChar(final String chars) {
|
||||
return chars.charAt(nextInt(chars.length()));
|
||||
}
|
||||
|
||||
public String randomString(final String chars, final int length) {
|
||||
final StringBuilder string = new StringBuilder();
|
||||
for (int i = 0; i < length; i++) {
|
||||
string.append(randomChar(chars));
|
||||
}
|
||||
return string.toString();
|
||||
}
|
||||
|
||||
public String randomString(final String chars, final int minLength, final int maxLength) {
|
||||
return randomString(chars, nextInt(minLength, maxLength));
|
||||
}
|
||||
|
||||
public boolean nextBoolean() {
|
||||
return random.nextBoolean();
|
||||
}
|
||||
|
||||
public int nextInt() {
|
||||
return random.nextInt();
|
||||
}
|
||||
|
||||
public int nextInt(final int min, final int max) {
|
||||
return nextInt(max - min + 1) + min;
|
||||
}
|
||||
|
||||
public int nextInt(final int n) {
|
||||
return random.nextInt(n);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public final <T> T randomItem(final T... items) {
|
||||
return items[nextInt(items.length)];
|
||||
}
|
||||
|
||||
public <T> T randomItem(final List<T> items) {
|
||||
return items.get(nextInt(items.size()));
|
||||
}
|
||||
|
||||
public Random getRandom() {
|
||||
return random;
|
||||
}
|
||||
|
||||
public <T> List<T> random(final int list, final Function<ExtendedRandom, T> generator) {
|
||||
return Stream.generate(() -> generator.apply(this)).limit(list).toList();
|
||||
}
|
||||
|
||||
public double nextDouble() {
|
||||
return random.nextDouble();
|
||||
}
|
||||
|
||||
public <E> void shuffle(final List<E> all) {
|
||||
Collections.shuffle(all, random);
|
||||
}
|
||||
}
|
||||
92
java/base/Functional.java
Normal file
92
java/base/Functional.java
Normal file
@@ -0,0 +1,92 @@
|
||||
package base;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
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 final class Functional {
|
||||
private Functional() {}
|
||||
|
||||
public static <T, R> List<R> map(final Collection<T> items, final Function<? super T, ? extends R> f) {
|
||||
return items.stream().map(f).collect(Collectors.toUnmodifiableList());
|
||||
}
|
||||
|
||||
public static <T, R> List<R> map(final List<T> items, final BiFunction<? super Integer, ? super T, ? extends R> f) {
|
||||
return IntStream.range(0, items.size())
|
||||
.mapToObj(i -> f.apply(i, items.get(i)))
|
||||
.collect(Collectors.toUnmodifiableList());
|
||||
}
|
||||
|
||||
public static <K, T, R> Map<K, R> mapValues(final Map<K, T> map, final Function<T, R> f) {
|
||||
return map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> f.apply(e.getValue())));
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <K, T> Map<K, T> mergeMaps(final Map<K, T>... maps) {
|
||||
return Stream.of(maps).flatMap(m -> m.entrySet().stream())
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a));
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> List<T> concat(final Collection<? extends T>... items) {
|
||||
final List<T> result = new ArrayList<>();
|
||||
for (final Collection<? extends T> item : items) {
|
||||
result.addAll(item);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> List<T> append(final Collection<T> collection, final T item) {
|
||||
final List<T> list = new ArrayList<>(collection);
|
||||
list.add(item);
|
||||
return list;
|
||||
}
|
||||
|
||||
public static <T> List<List<T>> allValues(final List<T> vals, final int length) {
|
||||
return Stream.generate(() -> vals)
|
||||
.limit(length)
|
||||
.reduce(
|
||||
List.of(List.of()),
|
||||
(prev, next) -> next.stream()
|
||||
.flatMap(value -> prev.stream().map(list -> append(list, value)))
|
||||
.toList(),
|
||||
(prev, next) -> next.stream()
|
||||
.flatMap(suffix -> prev.stream().map(prefix -> concat(prefix, suffix)))
|
||||
.toList()
|
||||
);
|
||||
}
|
||||
|
||||
public static <K, V> V get(final Map<K, V> map, final K key) {
|
||||
final V result = map.get(key);
|
||||
if (result == null) {
|
||||
throw new NullPointerException(key.toString() + " in " + map(map.keySet(), Objects::toString));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void addRange(final List<Integer> values, final int d, final int c) {
|
||||
for (int i = -d; i <= d; i++) {
|
||||
values.add(c + i);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> void forEachPair(final T[] items, final BiConsumer<? super T, ? super T> consumer) {
|
||||
assert items.length % 2 == 0;
|
||||
IntStream.range(0, items.length / 2).forEach(i -> consumer.accept(items[i * 2], items[i * 2 + 1]));
|
||||
}
|
||||
|
||||
|
||||
public static <T> List<Pair<T, T>> toPairs(final T[] items) {
|
||||
assert items.length % 2 == 0;
|
||||
return IntStream.range(0, items.length / 2)
|
||||
.mapToObj(i -> Pair.of(items[i * 2], items[i * 2 + 1]))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
56
java/base/Log.java
Normal file
56
java/base/Log.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package base;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class Log {
|
||||
private final Pattern LINES = Pattern.compile("\n");
|
||||
private int indent;
|
||||
|
||||
public static Supplier<Void> action(final Runnable action) {
|
||||
return () -> {
|
||||
action.run();
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
public void scope(final String name, final Runnable action) {
|
||||
scope(name, action(action));
|
||||
}
|
||||
|
||||
public <T> T scope(final String name, final Supplier<T> action) {
|
||||
println(name);
|
||||
indent++;
|
||||
try {
|
||||
return silentScope(name, action);
|
||||
} finally {
|
||||
indent--;
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T silentScope(final String ignoredName, final Supplier<T> action) {
|
||||
return action.get();
|
||||
}
|
||||
|
||||
@SuppressWarnings("UseOfSystemOutOrSystemErr")
|
||||
public void println(final Object value) {
|
||||
for (final String line : LINES.split(String.valueOf(value))) {
|
||||
System.out.println(indent() + line);
|
||||
}
|
||||
}
|
||||
|
||||
public void format(final String format, final Object... args) {
|
||||
println(String.format(format,args));
|
||||
}
|
||||
|
||||
private String indent() {
|
||||
return " ".repeat(indent);
|
||||
}
|
||||
|
||||
protected int getIndent() {
|
||||
return indent;
|
||||
}
|
||||
}
|
||||
28
java/base/MainChecker.java
Normal file
28
java/base/MainChecker.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package base;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
|
||||
public final class MainChecker {
|
||||
private final Runner runner;
|
||||
|
||||
public MainChecker(final Runner runner) {
|
||||
this.runner = runner;
|
||||
}
|
||||
|
||||
public List<String> run(final TestCounter counter, final String... input) {
|
||||
return runner.run(counter, input);
|
||||
}
|
||||
|
||||
public List<String> run(final TestCounter counter, final List<String> input) {
|
||||
return runner.run(counter, input);
|
||||
}
|
||||
|
||||
public void testEquals(final TestCounter counter, final List<String> input, final List<String> expected) {
|
||||
runner.testEquals(counter, input, expected);
|
||||
}
|
||||
|
||||
}
|
||||
15
java/base/Named.java
Normal file
15
java/base/Named.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package base;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public record Named<T>(String name, T value) {
|
||||
public static <T> Named<T> of(final String name, final T f) {
|
||||
return new Named<>(name, f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
44
java/base/Pair.java
Normal file
44
java/base/Pair.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package base;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.BinaryOperator;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
@SuppressWarnings({"StaticMethodOnlyUsedInOneClass", "unused"})
|
||||
public record Pair<F, S>(F first, S second) {
|
||||
public static <F, S> Pair<F, S> of(final F first, final S second) {
|
||||
return new Pair<>(first, second);
|
||||
}
|
||||
|
||||
public static <F, S> Pair<F, S> of(final Map.Entry<F, S> e) {
|
||||
return of(e.getKey(), e.getValue());
|
||||
}
|
||||
|
||||
public static <F, S> UnaryOperator<Pair<F, S>> lift(final UnaryOperator<F> f, final UnaryOperator<S> s) {
|
||||
return p -> of(f.apply(p.first), s.apply(p.second));
|
||||
}
|
||||
|
||||
public static <F, S> BinaryOperator<Pair<F, S>> lift(final BinaryOperator<F> f, final BinaryOperator<S> s) {
|
||||
return (p1, p2) -> of(f.apply(p1.first, p2.first), s.apply(p1.second, p2.second));
|
||||
}
|
||||
|
||||
public static <T, F, S> Function<T, Pair<F, S>> tee(
|
||||
final Function<? super T, ? extends F> f,
|
||||
final Function<? super T, ? extends S> s
|
||||
) {
|
||||
return t -> of(f.apply(t), s.apply(t));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "(" + first + ", " + second + ")";
|
||||
}
|
||||
|
||||
public <R> Pair<F, R> second(final R second) {
|
||||
return new Pair<>(first, second);
|
||||
}
|
||||
}
|
||||
185
java/base/Runner.java
Normal file
185
java/base/Runner.java
Normal file
@@ -0,0 +1,185 @@
|
||||
package base;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@FunctionalInterface
|
||||
public interface Runner {
|
||||
List<String> run(final TestCounter counter, final List<String> input);
|
||||
|
||||
default List<String> run(final TestCounter counter, final String... input) {
|
||||
return run(counter, List.of(input));
|
||||
}
|
||||
|
||||
default void testEquals(final TestCounter counter, final List<String> input, final List<String> expected) {
|
||||
counter.test(() -> {
|
||||
final List<String> actual = run(counter, input);
|
||||
for (int i = 0; i < Math.min(expected.size(), actual.size()); i++) {
|
||||
final String exp = expected.get(i);
|
||||
final String act = actual.get(i);
|
||||
if (!exp.equalsIgnoreCase(act)) {
|
||||
Asserts.assertEquals("Line " + (i + 1), exp, act);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Asserts.assertEquals("Number of lines", expected.size(), actual.size());
|
||||
});
|
||||
}
|
||||
|
||||
static Packages packages(final String... packages) {
|
||||
return new Packages(List.of(packages));
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface CommentRunner {
|
||||
List<String> run(String comment, TestCounter counter, List<String> input);
|
||||
}
|
||||
|
||||
final class Packages {
|
||||
private final List<String> packages;
|
||||
|
||||
private Packages(final List<String> packages) {
|
||||
this.packages = packages;
|
||||
}
|
||||
|
||||
public Runner std(final String className) {
|
||||
final CommentRunner main = main(className);
|
||||
return (counter, input) -> counter.call("io", () -> {
|
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try (final PrintWriter writer = new PrintWriter(baos, false, StandardCharsets.UTF_8)) {
|
||||
input.forEach(writer::println);
|
||||
}
|
||||
|
||||
final InputStream oldIn = System.in;
|
||||
try {
|
||||
System.setIn(new ByteArrayInputStream(baos.toByteArray()));
|
||||
return main.run(String.format("[%d input lines]", input.size()), counter, List.of());
|
||||
} finally {
|
||||
System.setIn(oldIn);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConfusingMainMethod")
|
||||
public CommentRunner main(final String className) {
|
||||
final Method method = findMain(className);
|
||||
|
||||
return (comment, counter, input) -> {
|
||||
counter.format("Running test %02d: java %s %s%n", counter.getTestNo(), method.getDeclaringClass().getName(), comment);
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
@SuppressWarnings("UseOfSystemOutOrSystemErr") final PrintStream oldOut = System.out;
|
||||
try {
|
||||
System.setOut(new PrintStream(out, false, StandardCharsets.UTF_8));
|
||||
method.invoke(null, new Object[]{input.toArray(String[]::new)});
|
||||
final BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(out.toByteArray()), StandardCharsets.UTF_8));
|
||||
final List<String> result = new ArrayList<>();
|
||||
while (true) {
|
||||
final String line = reader.readLine();
|
||||
if (line == null) {
|
||||
if (result.isEmpty()) {
|
||||
result.add("");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
result.add(line.trim());
|
||||
}
|
||||
} catch (final InvocationTargetException e) {
|
||||
final Throwable cause = e.getCause();
|
||||
throw Asserts.error("main thrown exception %s: %s", cause.getClass().getSimpleName(), cause);
|
||||
} catch (final Exception e) {
|
||||
throw Asserts.error("Cannot invoke main: %s: %s", e.getClass().getSimpleName(), e.getMessage());
|
||||
} finally {
|
||||
System.setOut(oldOut);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Method findMain(final String className) {
|
||||
try {
|
||||
final URL url = new File(".").toURI().toURL();
|
||||
final List<String> candidates = packages.stream()
|
||||
.flatMap(pkg -> {
|
||||
final String prefix = pkg.isEmpty() ? pkg : pkg + ".";
|
||||
return Stream.of(prefix + className, prefix + "$" + className);
|
||||
})
|
||||
.toList();
|
||||
|
||||
//noinspection ClassLoaderInstantiation,resource,IOResourceOpenedButNotSafelyClosed
|
||||
final URLClassLoader classLoader = new URLClassLoader(new URL[]{url});
|
||||
for (final String candidate : candidates) {
|
||||
try {
|
||||
final Class<?> loaded = classLoader.loadClass(candidate);
|
||||
if (!Modifier.isPublic(loaded.getModifiers())) {
|
||||
throw Asserts.error("Class %s is not public", candidate);
|
||||
}
|
||||
final Method main = loaded.getMethod("main", String[].class);
|
||||
if (!Modifier.isPublic(main.getModifiers()) || !Modifier.isStatic(main.getModifiers())) {
|
||||
throw Asserts.error("Method main of class %s should be public and static", candidate);
|
||||
}
|
||||
return main;
|
||||
} catch (final ClassNotFoundException e) {
|
||||
// Ignore
|
||||
} catch (final NoSuchMethodException e) {
|
||||
throw Asserts.error("Could not find method main(String[]) in class %s", candidate, e);
|
||||
}
|
||||
}
|
||||
throw Asserts.error("Could not find neither of classes %s", candidates);
|
||||
} catch (final MalformedURLException e) {
|
||||
throw Asserts.error("Invalid path", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getClassName(final String pkg, final String className) {
|
||||
return pkg.isEmpty() ? className : pkg + "." + className;
|
||||
}
|
||||
|
||||
public Runner args(final String className) {
|
||||
final CommentRunner main = main(className);
|
||||
// final AtomicReference<String> prev = new AtomicReference<>("");
|
||||
return (counter, input) -> {
|
||||
final int total = input.stream().mapToInt(String::length).sum() + input.size() * 3;
|
||||
final String comment = total <= 300
|
||||
? input.stream().collect(Collectors.joining("\" \"", "\"", "\""))
|
||||
: input.size() <= 100
|
||||
? String.format("[%d arguments, sizes: %s]", input.size(), input.stream()
|
||||
.mapToInt(String::length)
|
||||
.mapToObj(String::valueOf)
|
||||
.collect(Collectors.joining(", ")))
|
||||
: String.format("[%d arguments, total size: %d]", input.size(), total);
|
||||
// assert comment.length() <= 5 || !prev.get().equals(comment) : "Duplicate tests " + comment;
|
||||
// prev.set(comment);
|
||||
return main.run(comment, counter, input);
|
||||
};
|
||||
}
|
||||
|
||||
public Runner files(final String className) {
|
||||
final Runner args = args(className);
|
||||
return (counter, input) -> counter.call("io", () -> {
|
||||
final Path inf = counter.getFile("in");
|
||||
final Path ouf = counter.getFile("out");
|
||||
Files.write(inf, input);
|
||||
args.run(counter, List.of(inf.toString(), ouf.toString()));
|
||||
final List<String> output = Files.readAllLines(ouf);
|
||||
Files.delete(inf);
|
||||
Files.delete(ouf);
|
||||
return output;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
143
java/base/Selector.java
Normal file
143
java/base/Selector.java
Normal file
@@ -0,0 +1,143 @@
|
||||
package base;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class Selector {
|
||||
private final Class<?> owner;
|
||||
private final List<String> modes;
|
||||
private final Set<String> variantNames = new LinkedHashSet<>();
|
||||
private final Map<String, Consumer<TestCounter>> variants = new LinkedHashMap<>();
|
||||
|
||||
public Selector(final Class<?> owner, final String... modes) {
|
||||
this.owner = owner;
|
||||
this.modes = List.of(modes);
|
||||
}
|
||||
|
||||
public Selector variant(final String name, final Consumer<TestCounter> operations) {
|
||||
Asserts.assertTrue("Duplicate variant " + name, variants.put(name.toLowerCase(), operations) == null);
|
||||
variantNames.add(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
private static void check(final boolean condition, final String format, final Object... args) {
|
||||
if (!condition) {
|
||||
throw new IllegalArgumentException(String.format(format, args));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("UseOfSystemOutOrSystemErr")
|
||||
public void main(final String... args) {
|
||||
try {
|
||||
final String mode;
|
||||
if (modes.isEmpty()) {
|
||||
check(args.length >= 1, "At least one argument expected, found %s", args.length);
|
||||
mode = "";
|
||||
} else {
|
||||
check(args.length >= 2, "At least two arguments expected, found %s", args.length);
|
||||
mode = args[0];
|
||||
}
|
||||
|
||||
final List<String> vars = Arrays.stream(args).skip(modes.isEmpty() ? 0 : 1)
|
||||
.flatMap(arg -> Arrays.stream(arg.split("[ +]+")))
|
||||
.toList();
|
||||
|
||||
test(mode, vars);
|
||||
} catch (final IllegalArgumentException e) {
|
||||
System.err.println("ERROR: " + e.getMessage());
|
||||
if (modes.isEmpty()) {
|
||||
System.err.println("Usage: " + owner.getName() + " VARIANT...");
|
||||
} else {
|
||||
System.err.println("Usage: " + owner.getName() + " MODE VARIANT...");
|
||||
System.err.println("Modes: " + String.join(", ", modes));
|
||||
}
|
||||
System.err.println("Variants: " + String.join(", ", variantNames));
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
public void test(final String mode, List<String> vars) {
|
||||
final int modeNo = modes.isEmpty() ? -1 : modes.indexOf(mode) ;
|
||||
check(modes.isEmpty() || modeNo >= 0, "Unknown mode '%s'", mode);
|
||||
if (variantNames.contains("Base") && !vars.contains("Base")) {
|
||||
vars = new ArrayList<>(vars);
|
||||
vars.add(0, "Base");
|
||||
}
|
||||
|
||||
vars.forEach(variant -> check(variants.containsKey(variant.toLowerCase()), "Unknown variant '%s'", variant));
|
||||
|
||||
final Map<String, String> properties = modes.isEmpty()
|
||||
? Map.of("variant", String.join("+", vars))
|
||||
: Map.of("variant", String.join("+", vars), "mode", mode);
|
||||
final TestCounter counter = new TestCounter(owner, modeNo, properties);
|
||||
counter.printHead();
|
||||
vars.forEach(variant -> counter.scope("Testing " + variant, () -> variants.get(variant.toLowerCase()).accept(counter)));
|
||||
counter.printStatus();
|
||||
}
|
||||
|
||||
public static <V extends Tester> Composite<V> composite(final Class<?> owner, final Function<TestCounter, V> factory, final String... modes) {
|
||||
return new Composite<>(owner, factory, (tester, counter) -> tester.test(), modes);
|
||||
}
|
||||
|
||||
public static <V> Composite<V> composite(final Class<?> owner, final Function<TestCounter, V> factory, final BiConsumer<V, TestCounter> tester, final String... modes) {
|
||||
return new Composite<>(owner, factory, tester, modes);
|
||||
}
|
||||
|
||||
public List<String> getModes() {
|
||||
return modes.isEmpty() ? List.of("~") : modes;
|
||||
}
|
||||
|
||||
public List<String> getVariants() {
|
||||
return List.copyOf(variants.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public static final class Composite<V> {
|
||||
private final Selector selector;
|
||||
private final Function<TestCounter, V> factory;
|
||||
private final BiConsumer<V, TestCounter> tester;
|
||||
private List<Consumer<? super V>> base;
|
||||
|
||||
private Composite(final Class<?> owner, final Function<TestCounter, V> factory, final BiConsumer<V, TestCounter> tester, final String... modes) {
|
||||
selector = new Selector(owner, modes);
|
||||
this.factory = factory;
|
||||
this.tester = tester;
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public final Composite<V> variant(final String name, final Consumer<? super V>... parts) {
|
||||
if ("Base".equalsIgnoreCase(name)) {
|
||||
base = List.of(parts);
|
||||
return v(name.toLowerCase());
|
||||
} else {
|
||||
return v(name, parts);
|
||||
}
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
private Composite<V> v(final String name, final Consumer<? super V>... parts) {
|
||||
selector.variant(name, counter -> {
|
||||
final V variant = factory.apply(counter);
|
||||
for (final Consumer<? super V> part : base) {
|
||||
part.accept(variant);
|
||||
}
|
||||
for (final Consumer<? super V> part : parts) {
|
||||
part.accept(variant);
|
||||
}
|
||||
tester.accept(variant, counter);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
public Selector selector() {
|
||||
return selector;
|
||||
}
|
||||
}
|
||||
}
|
||||
184
java/base/TestCounter.java
Normal file
184
java/base/TestCounter.java
Normal file
@@ -0,0 +1,184 @@
|
||||
package base;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class TestCounter extends Log {
|
||||
public static final int DENOMINATOR = Integer.getInteger("base.denominator", 1);
|
||||
public static final int DENOMINATOR2 = (int) Math.round(Math.sqrt(DENOMINATOR));
|
||||
|
||||
private static final String JAR_EXT = ".jar";
|
||||
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
|
||||
|
||||
private final Class<?> owner;
|
||||
private final int mode;
|
||||
private final Map<String, ?> properties;
|
||||
private final ExtendedRandom random;
|
||||
|
||||
private final long start = System.currentTimeMillis();
|
||||
private int passed;
|
||||
|
||||
public TestCounter(final Class<?> owner, final int mode, final Map<String, ?> properties) {
|
||||
Locale.setDefault(Locale.US);
|
||||
Asserts.checkAssert(getClass());
|
||||
|
||||
this.owner = owner;
|
||||
this.mode = mode;
|
||||
this.properties = properties;
|
||||
random = new ExtendedRandom(owner);
|
||||
}
|
||||
|
||||
public int mode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public int getTestNo() {
|
||||
return passed + 1;
|
||||
}
|
||||
|
||||
public void test(final Runnable action) {
|
||||
testV(() -> {
|
||||
action.run();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public <T> void testForEach(final Iterable<? extends T> items, final Consumer<? super T> action) {
|
||||
for (final T item : items) {
|
||||
test(() -> action.accept(item));
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T testV(final Supplier<T> action) {
|
||||
return silentScope("Test " + getTestNo(), () -> {
|
||||
final T result = action.get();
|
||||
passed++;
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
private String getLine() {
|
||||
return getIndent() == 0 ? "=" : "-";
|
||||
}
|
||||
|
||||
public void printHead() {
|
||||
println("=== " + getTitle());
|
||||
}
|
||||
|
||||
public void printStatus() {
|
||||
format("%s%n%s%n", getLine().repeat(30), getTitle());
|
||||
format("%d tests passed in %dms%n", passed, System.currentTimeMillis() - start);
|
||||
println("Version: " + getVersion(owner));
|
||||
println("");
|
||||
}
|
||||
|
||||
private String getTitle() {
|
||||
return String.format("%s %s", owner.getSimpleName(), properties.isEmpty() ? "" : properties);
|
||||
}
|
||||
|
||||
private static String getVersion(final Class<?> clazz) {
|
||||
try {
|
||||
final ClassLoader cl = clazz.getClassLoader();
|
||||
final URL url = cl.getResource(clazz.getName().replace('.', '/') + ".class");
|
||||
if (url == null) {
|
||||
return "(no manifest)";
|
||||
}
|
||||
|
||||
final String path = url.getPath();
|
||||
final int index = path.indexOf(JAR_EXT);
|
||||
if (index == -1) {
|
||||
return DATE_FORMAT.format(new Date(new File(path).lastModified()));
|
||||
}
|
||||
|
||||
final String jarPath = path.substring(0, index + JAR_EXT.length());
|
||||
try (final JarFile jarFile = new JarFile(new File(new URI(jarPath)))) {
|
||||
final JarEntry entry = jarFile.getJarEntry("META-INF/MANIFEST.MF");
|
||||
return DATE_FORMAT.format(new Date(entry.getTime()));
|
||||
}
|
||||
} catch (final IOException | URISyntaxException e) {
|
||||
return "error: " + e;
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T call(final String message, final SupplierE<T> supplier) {
|
||||
return get(supplier).either(e -> fail(e, "%s", message), Function.identity());
|
||||
}
|
||||
|
||||
public void shouldFail(final String message, @SuppressWarnings("TypeMayBeWeakened") final RunnableE action) {
|
||||
test(() -> get(action).either(e -> null, v -> fail("%s", message)));
|
||||
}
|
||||
|
||||
public <T> T fail(final String format, final Object... args) {
|
||||
return fail(Asserts.error(format, args));
|
||||
}
|
||||
|
||||
public <T> T fail(final Throwable throwable) {
|
||||
return fail(throwable, "%s: %s", throwable.getClass().getSimpleName(), throwable.getMessage());
|
||||
}
|
||||
|
||||
public <T> T fail(final Throwable throwable, final String format, final Object... args) {
|
||||
final String message = String.format(format, args);
|
||||
println("ERROR: " + message);
|
||||
throw throwable instanceof Error ? (Error) throwable : new AssertionError(throwable);
|
||||
}
|
||||
|
||||
public void checkTrue(final boolean condition, final String message, final Object... args) {
|
||||
if (!condition) {
|
||||
fail(message, args);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> Either<Exception, T> get(final SupplierE<T> supplier) {
|
||||
return supplier.get();
|
||||
}
|
||||
|
||||
public Path getFile(final String suffix) {
|
||||
return Paths.get(String.format("test%d.%s", getTestNo(), suffix));
|
||||
}
|
||||
|
||||
public ExtendedRandom random() {
|
||||
return random;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface SupplierE<T> extends Supplier<Either<Exception, T>> {
|
||||
T getE() throws Exception;
|
||||
|
||||
@Override
|
||||
default Either<Exception, T> get() {
|
||||
try {
|
||||
return Either.right(getE());
|
||||
} catch (final Exception e) {
|
||||
return Either.left(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface RunnableE extends SupplierE<Void> {
|
||||
void run() throws Exception;
|
||||
|
||||
@Override
|
||||
default Void getE() throws Exception {
|
||||
run();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
java/base/Tester.java
Normal file
18
java/base/Tester.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package base;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public abstract class Tester extends BaseChecker {
|
||||
protected Tester(final TestCounter counter) {
|
||||
super(counter);
|
||||
}
|
||||
|
||||
public abstract void test();
|
||||
|
||||
public void run(final Class<?> test, final String... args) {
|
||||
System.out.println("=== Testing " + test.getSimpleName() + " " + String.join(" ", args));
|
||||
test();
|
||||
counter.printStatus();
|
||||
}
|
||||
}
|
||||
15
java/base/Unit.java
Normal file
15
java/base/Unit.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package base;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class Unit {
|
||||
public static final Unit INSTANCE = new Unit();
|
||||
|
||||
private Unit() { }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "unit";
|
||||
}
|
||||
}
|
||||
7
java/base/package-info.java
Normal file
7
java/base/package-info.java
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Common homeworks test classes
|
||||
* of <a href="https://www.kgeorgiy.info/courses/prog-intro/">Introduction to Programming</a> course.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
package base;
|
||||
202
java/expression/BigDecimalListExpression.java
Normal file
202
java/expression/BigDecimalListExpression.java
Normal file
@@ -0,0 +1,202 @@
|
||||
package expression;
|
||||
|
||||
import base.Asserts;
|
||||
import base.Pair;
|
||||
import base.TestCounter;
|
||||
import expression.common.ExpressionKind;
|
||||
import expression.common.Type;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* One-argument arithmetic expression over {@link BigDecimal}s.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@SuppressWarnings("ClassReferencesSubclass")
|
||||
public interface BigDecimalListExpression extends ToMiniString {
|
||||
BigDecimal evaluateBd(List<BigDecimal> variables);
|
||||
|
||||
// Tests follow. You may temporarily remove everything til the end.
|
||||
|
||||
Add EXAMPLE = new Add(
|
||||
new Subtract(new Variable(0), new Const(BigDecimal.ONE)),
|
||||
new Multiply(new Variable(1), new Const(BigDecimal.TEN))
|
||||
);
|
||||
|
||||
Type<BigDecimal> TYPE = new Type<>(
|
||||
v -> new BigDecimal(v + ".000"),
|
||||
random -> BigDecimal.valueOf(random.getRandom().nextGaussian()),
|
||||
BigDecimal.class
|
||||
);
|
||||
ExpressionKind<BigDecimalListExpression, BigDecimal> KIND = new ExpressionKind<>(
|
||||
TYPE,
|
||||
BigDecimalListExpression.class,
|
||||
(r, c) -> IntStream.range(0, c)
|
||||
.mapToObj(name -> Pair.<String, BigDecimalListExpression>of("$" + name, new Variable(name)))
|
||||
.toList(),
|
||||
(expr, variables, values) -> expr.evaluateBd(values)
|
||||
);
|
||||
|
||||
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
|
||||
static ExpressionTester<?, ?> tester(final TestCounter counter) {
|
||||
Asserts.assertEquals("Example toString()", "(($0 - 1) + ($1 * 10))", EXAMPLE.toString());
|
||||
Asserts.assertEquals(
|
||||
EXAMPLE + " at (2, 3)",
|
||||
BigDecimal.valueOf(31),
|
||||
EXAMPLE.evaluateBd(List.of(BigDecimal.valueOf(2), BigDecimal.valueOf(3)))
|
||||
);
|
||||
|
||||
final Variable vx = new Variable(0);
|
||||
final Variable vy = new Variable(1);
|
||||
|
||||
return new ExpressionTester<>(
|
||||
counter, KIND, c -> v -> c,
|
||||
(op, a, b) -> v -> op.apply(a.evaluateBd(v), b.evaluateBd(v)),
|
||||
BigDecimal::add, BigDecimal::subtract, BigDecimal::multiply, BigDecimal::divide
|
||||
)
|
||||
.basic("10", "10", v -> v(10), c(10))
|
||||
.basic("$x", "$x", BigDecimalListExpression::x, vx)
|
||||
.basic("$y", "$y", BigDecimalListExpression::y, vy)
|
||||
.basic("($x + $y)", "$x + $y", v -> x(v).add(y(v)), new Add(vx, vy))
|
||||
.basic("($x + 2)", "$x + 2", v -> x(v).add(v(2)), new Add(vx, c(2)))
|
||||
.basic("(2 - $x)", "2 - $x", v -> v(2).subtract(x(v)), new Subtract(c(2), vx))
|
||||
.basic("(3 * $x)", "3 * $x", v -> v(3).multiply(x(v)), new Multiply(c(3), vx))
|
||||
.basic("($x + $x)", "$x + $x", v -> x(v).add(x(v)), new Add(vx, vx))
|
||||
.basic("($x / -2)", "$x / -2", v -> x(v).divide(v(-2)), new Divide(vx, c(-2)))
|
||||
.basic("(2 + $x)", "2 + $x", v -> v(2).add(x(v)), new Add(c(2), vx))
|
||||
.basic("((1 + 2) + 3)", "1 + 2 + 3", v -> v(6), new Add(new Add(c(1), c(2)), c(3)))
|
||||
.basic("(1 + (2 * 3))", "1 + 2 * 3", v -> v(7), new Add(c(1), new Multiply(c(2), c(3))))
|
||||
.basic("(1 - (2 * 3))", "1 - 2 * 3", v -> v(-5), new Subtract(c(1), new Multiply(c(2), c(3))))
|
||||
.basic("(1 + (2 + 3))", "1 + 2 + 3", v -> v(6), new Add(c(1), new Add(c(2), c(3))))
|
||||
.basic("((1 - 2) - 3)", "1 - 2 - 3", v -> v(-4), new Subtract(new Subtract(c(1), c(2)), c(3)))
|
||||
.basic("(1 - (2 - 3))", "1 - (2 - 3)", v -> v(2), new Subtract(c(1), new Subtract(c(2), c(3))))
|
||||
.basic("((1 * 2) * 3)", "1 * 2 * 3", v -> v(6), new Multiply(new Multiply(c(1), c(2)), c(3)))
|
||||
.basic("(1 * (2 * 3))", "1 * 2 * 3", v -> v(6), new Multiply(c(1), new Multiply(c(2), c(3))))
|
||||
.basic("((10 / 2) / 3)", "10 / 2 / 3", v -> v(10).divide(v(2)).divide(v(3)), new Divide(new Divide(c(10), c(2)), c(3)))
|
||||
.basic("(10 / (3 / 2))", "10 / (3 / 2)", v -> v(10).divide(v(3).divide(v(2))), new Divide(c(10), new Divide(c(3), c(2))))
|
||||
.basic("(($x * $x) + (($x - 1) / 10))",
|
||||
"$x * $x + ($x - 1) / 10",
|
||||
v -> x(v).multiply(x(v)).add(x(v).subtract(v(1)).divide(v(10))),
|
||||
new Add(new Multiply(vx, vx), new Divide(new Subtract(vx, c(1)), c(10)))
|
||||
)
|
||||
.basic("($x * -1000000000)", "$x * -1000000000", v -> x(v).multiply(v(-1_000_000_000)), new Multiply(vx, c(-1_000_000_000)))
|
||||
.basic("($x * -1000000000000000)", "$x * -1000000000000000", v -> x(v).multiply(v(-1_000_000_000_000_000L)), new Multiply(vx, c(-1_000_000_000_000_000L)))
|
||||
.basic("(10 / $x)", "10 / $x", v -> v(10).divide(x(v)), new Divide(c(10), vx))
|
||||
.basic("($x / $x)", "$x / $x", v -> x(v).divide(x(v)), new Divide(vx, vx))
|
||||
|
||||
.advanced("(2 + 1)", "2 + 1", v -> v(2 + 1), new Add(c(2), c(1)))
|
||||
.advanced("($x - 1)", "$x - 1", v -> x(v).subtract(v(1)), new Subtract(vx, c(1)))
|
||||
.advanced("(1 * 2)", "1 * 2", v -> v(1 * 2), new Multiply(c(1), c(2)))
|
||||
.advanced("($x / 1)", "$x / 1", v -> x(v).divide(v(1)), new Divide(vx, c(1)))
|
||||
.advanced("(1 + (2 + 1))", "1 + 2 + 1", v -> v(1 + 2 + 1), new Add(c(1), new Add(c(2), c(1))))
|
||||
.advanced("($x - ($x - 1))", "$x - ($x - 1)", v -> x(v).subtract(x(v).subtract(v(1))), new Subtract(vx, new Subtract(vx, c(1))))
|
||||
.advanced("(2 * ($x / 1))", "2 * ($x / 1)", v -> v(2).multiply(x(v).divide(v(1))), new Multiply(c(2), new Divide(vx, c(1))))
|
||||
.advanced("(2 / ($x - 1))", "2 / ($x - 1)", v -> v(2).divide(x(v).subtract(v(1))), new Divide(c(2), new Subtract(vx, c(1))))
|
||||
.advanced("((1 * 2) + $x)", "1 * 2 + $x", v -> v(1 * 2).add(x(v)), new Add(new Multiply(c(1), c(2)), vx))
|
||||
.advanced("(($x - 1) - 2)", "$x - 1 - 2", v -> x(v).subtract(v(3)), new Subtract(new Subtract(vx, c(1)), c(2)))
|
||||
.advanced("(($x / 1) * 2)", "$x / 1 * 2", v -> x(v).multiply(v(2)), new Multiply(new Divide(vx, c(1)), c(2)))
|
||||
.advanced("((2 + 1) / 1)", "(2 + 1) / 1", v -> v(3), new Divide(new Add(c(2), c(1)), c(1)))
|
||||
.advanced(
|
||||
"(1 + (1 + (2 + 1)))",
|
||||
"1 + 1 + 2 + 1",
|
||||
v -> v(1 + 1 + 2 + 1),
|
||||
new Add(c(1), new Add(c(1), new Add(c(2), c(1))))
|
||||
)
|
||||
.advanced(
|
||||
"($x - ((1 * 2) + $x))",
|
||||
"$x - (1 * 2 + $x)",
|
||||
v -> x(v).subtract(v(1 * 2).add(x(v))),
|
||||
new Subtract(vx, new Add(new Multiply(c(1), c(2)), vx))
|
||||
)
|
||||
.advanced(
|
||||
"($x * (2 / ($x - 1)))",
|
||||
"$x * (2 / ($x - 1))",
|
||||
v -> x(v).multiply(v(2).divide(x(v).subtract(v(1)))),
|
||||
new Multiply(vx, new Divide(c(2), new Subtract(vx, c(1))))
|
||||
)
|
||||
.advanced(
|
||||
"($x / (1 + (2 + 1)))",
|
||||
"$x / (1 + 2 + 1)",
|
||||
v -> x(v).divide(v(1 + 2 + 1)),
|
||||
new Divide(vx, new Add(c(1), new Add(c(2), c(1))))
|
||||
)
|
||||
.advanced(
|
||||
"((1 * 2) + (2 + 1))",
|
||||
"1 * 2 + 2 + 1",
|
||||
v -> v(1 * 2 + 2 + 1),
|
||||
new Add(new Multiply(c(1), c(2)), new Add(c(2), c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"((2 + 1) - (2 + 1))",
|
||||
"2 + 1 - (2 + 1)",
|
||||
v -> v(2 + 1 - (2 + 1)),
|
||||
new Subtract(new Add(c(2), c(1)), new Add(c(2), c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"(($x - 1) * ($x / 1))",
|
||||
"($x - 1) * ($x / 1)",
|
||||
v -> x(v).subtract(v(1)).multiply(x(v).divide(v(1))),
|
||||
new Multiply(new Subtract(vx, c(1)), new Divide(vx, c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"(($x - 1) / (1 * 2))",
|
||||
"($x - 1) / (1 * 2)",
|
||||
v -> x(v).subtract(v(1)).divide(v(2)),
|
||||
new Divide(new Subtract(vx, c(1)), new Multiply(c(1), c(2)))
|
||||
)
|
||||
.advanced(
|
||||
"((($x - 1) - 2) + $x)",
|
||||
"$x - 1 - 2 + $x",
|
||||
v -> x(v).subtract(v(3)).add(x(v)),
|
||||
new Add(new Subtract(new Subtract(vx, c(1)), c(2)), vx)
|
||||
)
|
||||
.advanced(
|
||||
"(((1 * 2) + $x) - 1)",
|
||||
"1 * 2 + $x - 1",
|
||||
v -> v(1).add(x(v)),
|
||||
new Subtract(new Add(new Multiply(c(1), c(2)), vx), c(1))
|
||||
)
|
||||
.advanced(
|
||||
"(((2 + 1) / 1) * $x)",
|
||||
"(2 + 1) / 1 * $x",
|
||||
v -> v(3).multiply(x(v)),
|
||||
new Multiply(new Divide(new Add(c(2), c(1)), c(1)), vx)
|
||||
)
|
||||
.advanced(
|
||||
"((2 / ($x - 1)) / 2)",
|
||||
"2 / ($x - 1) / 2",
|
||||
v -> v(2).divide(x(v).subtract(v(1))).divide(v(2)),
|
||||
new Divide(new Divide(c(2), new Subtract(vx, c(1))), c(2))
|
||||
);
|
||||
}
|
||||
|
||||
private static BigDecimal x(final List<BigDecimal> vars) {
|
||||
return vars.get(0);
|
||||
}
|
||||
|
||||
private static BigDecimal y(final List<BigDecimal> vars) {
|
||||
return vars.get(1);
|
||||
}
|
||||
|
||||
private static Const c(final BigDecimal v) {
|
||||
return TYPE.constant(v);
|
||||
}
|
||||
|
||||
private static Const c(final long v) {
|
||||
return TYPE.constant(v(v));
|
||||
}
|
||||
|
||||
private static BigDecimal v(final long v) {
|
||||
return BigDecimal.valueOf(v);
|
||||
}
|
||||
|
||||
static void main(final String... args) {
|
||||
TripleExpression.SELECTOR
|
||||
.variant("BigDecimalList", ExpressionTest.v(BigDecimalListExpression::tester))
|
||||
.main(args);
|
||||
}
|
||||
}
|
||||
192
java/expression/BigIntegerListExpression.java
Normal file
192
java/expression/BigIntegerListExpression.java
Normal file
@@ -0,0 +1,192 @@
|
||||
package expression;
|
||||
|
||||
import base.Asserts;
|
||||
import base.Pair;
|
||||
import base.TestCounter;
|
||||
import expression.common.ExpressionKind;
|
||||
import expression.common.Type;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@SuppressWarnings("ClassReferencesSubclass")
|
||||
public interface BigIntegerListExpression extends ToMiniString {
|
||||
BigInteger evaluateBi(List<BigInteger> variables);
|
||||
|
||||
// Tests follow. You may temporarily remove everything til the end.
|
||||
|
||||
Add EXAMPLE = new Add(
|
||||
new Subtract(new Variable(0), new Const(BigInteger.ONE)),
|
||||
new Multiply(new Variable(1), new Const(BigInteger.TEN))
|
||||
);
|
||||
|
||||
Type<BigInteger> TYPE = new Type<>(BigInteger::valueOf, random -> v(random.getRandom().nextLong()), BigInteger.class);
|
||||
ExpressionKind<BigIntegerListExpression, BigInteger> KIND = new ExpressionKind<>(
|
||||
TYPE,
|
||||
BigIntegerListExpression.class,
|
||||
(r, c) -> IntStream.range(0, c)
|
||||
.mapToObj(name -> Pair.<String, BigIntegerListExpression>of("$" + name, new Variable(name)))
|
||||
.toList(),
|
||||
(expr, variables, values) -> expr.evaluateBi(values)
|
||||
);
|
||||
|
||||
@SuppressWarnings("PointlessArithmeticExpression")
|
||||
static ExpressionTester<?, ?> tester(final TestCounter counter) {
|
||||
Asserts.assertEquals("Example toString()", "(($0 - 1) + ($1 * 10))", EXAMPLE.toString());
|
||||
Asserts.assertEquals(
|
||||
EXAMPLE + " at (2, 3)",
|
||||
BigInteger.valueOf(31),
|
||||
EXAMPLE.evaluateBi(List.of(BigInteger.valueOf(2), BigInteger.valueOf(3)))
|
||||
);
|
||||
|
||||
final Variable vx = new Variable(0);
|
||||
final Variable vy = new Variable(1);
|
||||
|
||||
return new ExpressionTester<>(
|
||||
counter, KIND, c -> v -> c,
|
||||
(op, a, b) -> v -> op.apply(a.evaluateBi(v), b.evaluateBi(v)),
|
||||
BigInteger::add, BigInteger::subtract, BigInteger::multiply, BigInteger::divide
|
||||
)
|
||||
.basic("10", "10", v -> v(10), c(10))
|
||||
.basic("$x", "$x", BigIntegerListExpression::x, vx)
|
||||
.basic("$y", "$y", BigIntegerListExpression::y, vy)
|
||||
.basic("($x + $y)", "$x + $y", v -> x(v).add(y(v)), new Add(vx, vy))
|
||||
.basic("($x + 2)", "$x + 2", v -> x(v).add(v(2)), new Add(vx, c(2)))
|
||||
.basic("(2 - $x)", "2 - $x", v -> v(2).subtract(x(v)), new Subtract(c(2), vx))
|
||||
.basic("(3 * $x)", "3 * $x", v -> v(3).multiply(x(v)), new Multiply(c(3), vx))
|
||||
.basic("($x + $x)", "$x + $x", v -> x(v).add(x(v)), new Add(vx, vx))
|
||||
.basic("($x / -2)", "$x / -2", v -> x(v).divide(v(-2)), new Divide(vx, c(-2)))
|
||||
.basic("(2 + $x)", "2 + $x", v -> v(2).add(x(v)), new Add(c(2), vx))
|
||||
.basic("((1 + 2) + 3)", "1 + 2 + 3", v -> v(6), new Add(new Add(c(1), c(2)), c(3)))
|
||||
.basic("(1 + (2 * 3))", "1 + 2 * 3", v -> v(7), new Add(c(1), new Multiply(c(2), c(3))))
|
||||
.basic("(1 - (2 * 3))", "1 - 2 * 3", v -> v(-5), new Subtract(c(1), new Multiply(c(2), c(3))))
|
||||
.basic("(1 + (2 + 3))", "1 + 2 + 3", v -> v(6), new Add(c(1), new Add(c(2), c(3))))
|
||||
.basic("((1 - 2) - 3)", "1 - 2 - 3", v -> v(-4), new Subtract(new Subtract(c(1), c(2)), c(3)))
|
||||
.basic("(1 - (2 - 3))", "1 - (2 - 3)", v -> v(2), new Subtract(c(1), new Subtract(c(2), c(3))))
|
||||
.basic("((1 * 2) * 3)", "1 * 2 * 3", v -> v(6), new Multiply(new Multiply(c(1), c(2)), c(3)))
|
||||
.basic("(1 * (2 * 3))", "1 * 2 * 3", v -> v(6), new Multiply(c(1), new Multiply(c(2), c(3))))
|
||||
.basic("((10 / 2) / 3)", "10 / 2 / 3", v -> v(10 / 2 / 3), new Divide(new Divide(c(10), c(2)), c(3)))
|
||||
.basic("(10 / (3 / 2))", "10 / (3 / 2)", v -> v(10 / (3 / 2)), new Divide(c(10), new Divide(c(3), c(2))))
|
||||
.basic("(($x * $x) + (($x - 1) / 10))",
|
||||
"$x * $x + ($x - 1) / 10",
|
||||
v -> x(v).multiply(x(v)).add(x(v).subtract(v(1)).divide(v(10))),
|
||||
new Add(new Multiply(vx, vx), new Divide(new Subtract(vx, c(1)), c(10)))
|
||||
)
|
||||
.basic("($x * -1000000000)", "$x * -1000000000", v -> x(v).multiply(v(-1_000_000_000)), new Multiply(vx, c(-1_000_000_000)))
|
||||
.basic("($x * -1000000000000000)", "$x * -1000000000000000", v -> x(v).multiply(v(-1_000_000_000_000_000L)), new Multiply(vx, c(-1_000_000_000_000_000L)))
|
||||
.basic("(10 / $x)", "10 / $x", v -> v(10).divide(x(v)), new Divide(c(10), vx))
|
||||
.basic("($x / $x)", "$x / $x", v -> x(v).divide(x(v)), new Divide(vx, vx))
|
||||
|
||||
.advanced("(2 + 1)", "2 + 1", v -> v(2 + 1), new Add(c(2), c(1)))
|
||||
.advanced("($x - 1)", "$x - 1", v -> x(v).subtract(v(1)), new Subtract(vx, c(1)))
|
||||
.advanced("(1 * 2)", "1 * 2", v -> v(1 * 2), new Multiply(c(1), c(2)))
|
||||
.advanced("($x / 1)", "$x / 1", v -> x(v).divide(v(1)), new Divide(vx, c(1)))
|
||||
.advanced("(1 + (2 + 1))", "1 + 2 + 1", v -> v(1 + 2 + 1), new Add(c(1), new Add(c(2), c(1))))
|
||||
.advanced("($x - ($x - 1))", "$x - ($x - 1)", v -> x(v).subtract(x(v).subtract(v(1))), new Subtract(vx, new Subtract(vx, c(1))))
|
||||
.advanced("(2 * ($x / 1))", "2 * ($x / 1)", v -> v(2).multiply(x(v).divide(v(1))), new Multiply(c(2), new Divide(vx, c(1))))
|
||||
.advanced("(2 / ($x - 1))", "2 / ($x - 1)", v -> v(2).divide(x(v).subtract(v(1))), new Divide(c(2), new Subtract(vx, c(1))))
|
||||
.advanced("((1 * 2) + $x)", "1 * 2 + $x", v -> v(1 * 2).add(x(v)), new Add(new Multiply(c(1), c(2)), vx))
|
||||
.advanced("(($x - 1) - 2)", "$x - 1 - 2", v -> x(v).subtract(v(3)), new Subtract(new Subtract(vx, c(1)), c(2)))
|
||||
.advanced("(($x / 1) * 2)", "$x / 1 * 2", v -> x(v).multiply(v(2)), new Multiply(new Divide(vx, c(1)), c(2)))
|
||||
.advanced("((2 + 1) / 1)", "(2 + 1) / 1", v -> v(3), new Divide(new Add(c(2), c(1)), c(1)))
|
||||
.advanced(
|
||||
"(1 + (1 + (2 + 1)))",
|
||||
"1 + 1 + 2 + 1",
|
||||
v -> v(1 + 1 + 2 + 1),
|
||||
new Add(c(1), new Add(c(1), new Add(c(2), c(1))))
|
||||
)
|
||||
.advanced(
|
||||
"($x - ((1 * 2) + $x))",
|
||||
"$x - (1 * 2 + $x)",
|
||||
v -> x(v).subtract(v(1 * 2).add(x(v))),
|
||||
new Subtract(vx, new Add(new Multiply(c(1), c(2)), vx))
|
||||
)
|
||||
.advanced(
|
||||
"($x * (2 / ($x - 1)))",
|
||||
"$x * (2 / ($x - 1))",
|
||||
v -> x(v).multiply(v(2).divide(x(v).subtract(v(1)))),
|
||||
new Multiply(vx, new Divide(c(2), new Subtract(vx, c(1))))
|
||||
)
|
||||
.advanced(
|
||||
"($x / (1 + (2 + 1)))",
|
||||
"$x / (1 + 2 + 1)",
|
||||
v -> x(v).divide(v(1 + 2 + 1)),
|
||||
new Divide(vx, new Add(c(1), new Add(c(2), c(1))))
|
||||
)
|
||||
.advanced(
|
||||
"((1 * 2) + (2 + 1))",
|
||||
"1 * 2 + 2 + 1",
|
||||
v -> v(1 * 2 + 2 + 1),
|
||||
new Add(new Multiply(c(1), c(2)), new Add(c(2), c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"((2 + 1) - (2 + 1))",
|
||||
"2 + 1 - (2 + 1)",
|
||||
v -> v(2 + 1 - (2 + 1)),
|
||||
new Subtract(new Add(c(2), c(1)), new Add(c(2), c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"(($x - 1) * ($x / 1))",
|
||||
"($x - 1) * ($x / 1)",
|
||||
v -> x(v).subtract(v(1)).multiply(x(v).divide(v(1))),
|
||||
new Multiply(new Subtract(vx, c(1)), new Divide(vx, c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"(($x - 1) / (1 * 2))",
|
||||
"($x - 1) / (1 * 2)",
|
||||
v -> x(v).subtract(v(1)).divide(v(2)),
|
||||
new Divide(new Subtract(vx, c(1)), new Multiply(c(1), c(2)))
|
||||
)
|
||||
.advanced(
|
||||
"((($x - 1) - 2) + $x)",
|
||||
"$x - 1 - 2 + $x",
|
||||
v -> x(v).subtract(v(3)).add(x(v)),
|
||||
new Add(new Subtract(new Subtract(vx, c(1)), c(2)), vx)
|
||||
)
|
||||
.advanced(
|
||||
"(((1 * 2) + $x) - 1)",
|
||||
"1 * 2 + $x - 1",
|
||||
v -> v(1).add(x(v)),
|
||||
new Subtract(new Add(new Multiply(c(1), c(2)), vx), c(1))
|
||||
)
|
||||
.advanced(
|
||||
"(((2 + 1) / 1) * $x)",
|
||||
"(2 + 1) / 1 * $x",
|
||||
v -> v(3).multiply(x(v)),
|
||||
new Multiply(new Divide(new Add(c(2), c(1)), c(1)), vx)
|
||||
)
|
||||
.advanced(
|
||||
"((2 / ($x - 1)) / 2)",
|
||||
"2 / ($x - 1) / 2",
|
||||
v -> v(2).divide(x(v).subtract(v(1))).divide(v(2)),
|
||||
new Divide(new Divide(c(2), new Subtract(vx, c(1))), c(2))
|
||||
);
|
||||
}
|
||||
|
||||
private static BigInteger x(final List<BigInteger> vars) {
|
||||
return vars.get(0);
|
||||
}
|
||||
|
||||
private static BigInteger y(final List<BigInteger> vars) {
|
||||
return vars.get(1);
|
||||
}
|
||||
|
||||
private static Const c(final long v) {
|
||||
return TYPE.constant(v(v));
|
||||
}
|
||||
|
||||
private static BigInteger v(final long v) {
|
||||
return BigInteger.valueOf(v);
|
||||
}
|
||||
|
||||
static void main(final String... args) {
|
||||
TripleExpression.SELECTOR
|
||||
.variant("BigIntegerList", ExpressionTest.v(BigIntegerListExpression::tester))
|
||||
.main(args);
|
||||
}
|
||||
}
|
||||
112
java/expression/Expression.java
Normal file
112
java/expression/Expression.java
Normal file
@@ -0,0 +1,112 @@
|
||||
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<Integer> TYPE = new Type<>(a -> a, ExtendedRandom::nextInt, int.class);
|
||||
ExpressionKind<Expression, Integer> 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));
|
||||
}
|
||||
}
|
||||
26
java/expression/ExpressionTest.java
Normal file
26
java/expression/ExpressionTest.java
Normal file
@@ -0,0 +1,26 @@
|
||||
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<TestCounter> v(final Function<TestCounter, ? extends ExpressionTester<?, ?>> tester) {
|
||||
return t -> tester.apply(t).test();
|
||||
}
|
||||
|
||||
public static void main(final String... args) {
|
||||
SELECTOR.main(args);
|
||||
}
|
||||
}
|
||||
288
java/expression/ExpressionTester.java
Normal file
288
java/expression/ExpressionTester.java
Normal file
@@ -0,0 +1,288 @@
|
||||
package expression;
|
||||
|
||||
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;
|
||||
|
||||
import static base.Asserts.assertTrue;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class ExpressionTester<E extends ToMiniString, C> extends Tester {
|
||||
private final List<Integer> VALUES = IntStream.rangeClosed(-10, 10).boxed().toList();
|
||||
private final ExpressionKind<E, C> kind;
|
||||
|
||||
private final List<Test> basic = new ArrayList<>();
|
||||
private final List<Test> advanced = new ArrayList<>();
|
||||
private final Set<String> used = new HashSet<>();
|
||||
private final GeneratorBuilder generator;
|
||||
|
||||
private final List<Pair<ToMiniString, String>> prev = new ArrayList<>();
|
||||
private final Map<String, C> mappings;
|
||||
|
||||
protected ExpressionTester(
|
||||
final TestCounter counter,
|
||||
final ExpressionKind<E, C> kind,
|
||||
final Function<C, E> expectedConstant,
|
||||
final Binary<C, E> binary,
|
||||
final BinaryOperator<C> add,
|
||||
final BinaryOperator<C> sub,
|
||||
final BinaryOperator<C> mul,
|
||||
final BinaryOperator<C> div,
|
||||
final Map<String, C> 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<E, C> kind,
|
||||
final Function<C, E> expectedConstant,
|
||||
final Binary<C, E> binary,
|
||||
final BinaryOperator<C> add,
|
||||
final BinaryOperator<C> sub,
|
||||
final BinaryOperator<C> mul,
|
||||
final BinaryOperator<C> 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<ToMiniString, String> 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<String> variables,
|
||||
final List<C> 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<String> variables, final List<C> values) {
|
||||
try {
|
||||
return kind.evaluate(expression, variables, values);
|
||||
} catch (final Exception e) {
|
||||
return e.getClass().getName();
|
||||
}
|
||||
}
|
||||
|
||||
protected ExpressionTester<E, C> basic(final String full, final String mini, final E expected, final E actual) {
|
||||
return basicF(full, mini, expected, vars -> actual);
|
||||
}
|
||||
|
||||
protected ExpressionTester<E, C> basicF(final String full, final String mini, final E expected, final Function<List<String>, E> actual) {
|
||||
return basic(new Test(full, mini, expected, actual));
|
||||
}
|
||||
|
||||
private ExpressionTester<E, C> basic(final Test test) {
|
||||
Asserts.assertTrue(test.full, used.add(test.full));
|
||||
basic.add(test);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected ExpressionTester<E, C> advanced(final String full, final String mini, final E expected, final E actual) {
|
||||
return advancedF(full, mini, expected, vars -> actual);
|
||||
}
|
||||
|
||||
protected ExpressionTester<E, C> advancedF(final String full, final String mini, final E expected, final Function<List<String>, E> actual) {
|
||||
Asserts.assertTrue(full, used.add(full));
|
||||
advanced.add(new Test(full, mini, expected, actual));
|
||||
return this;
|
||||
}
|
||||
|
||||
protected static <E> Named<E> variable(final String name, final E expected) {
|
||||
return Named.of(name, expected);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Binary<C, E> {
|
||||
E apply(BinaryOperator<C> op, E a, E b);
|
||||
}
|
||||
|
||||
private final class Test {
|
||||
private final String full;
|
||||
private final String mini;
|
||||
private final E expected;
|
||||
private final Function<List<String>, E> actual;
|
||||
|
||||
private Test(final String full, final String mini, final E expected, final Function<List<String>, E> actual) {
|
||||
this.full = full;
|
||||
this.mini = mini;
|
||||
this.expected = expected;
|
||||
this.actual = actual;
|
||||
}
|
||||
|
||||
private void test() {
|
||||
final List<Pair<String, E>> variables = kind.variables().generate(random(), 3);
|
||||
final List<String> 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<String> names) {
|
||||
for (int i = 0; i < names.size(); i++) {
|
||||
string = string.replace("$" + (char) ('x' + i), names.get(i));
|
||||
}
|
||||
for (final Map.Entry<String, C> mapping : mappings.entrySet()) {
|
||||
string = string.replace(mapping.getKey(), mapping.getValue().toString());
|
||||
}
|
||||
return string;
|
||||
}
|
||||
}
|
||||
|
||||
private final class GeneratorBuilder {
|
||||
private final Generator.Builder<C> generator;
|
||||
private final NodeRendererBuilder<C> renderer = new NodeRendererBuilder<>(random());
|
||||
private final Renderer.Builder<C, Unit, E> expected;
|
||||
private final Renderer.Builder<C, Unit, E> actual;
|
||||
private final Renderer.Builder<C, Unit, E> copy;
|
||||
private final Binary<C, E> binary;
|
||||
|
||||
private GeneratorBuilder(
|
||||
final Function<C, E> expectedConstant,
|
||||
final Function<? super C, E> actualConstant,
|
||||
final Binary<C, E> binary,
|
||||
final Function<ExtendedRandom, C> 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<C> 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<? extends E> 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<Unit, E> 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<C> renderer = this.renderer.build();
|
||||
final Renderer<C, Unit, E> expectedRenderer = this.expected.build();
|
||||
final Renderer<C, Unit, E> actualRenderer = this.actual.build();
|
||||
final expression.common.Generator<C, E> 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<Pair<String, E>> variables = expr.variables();
|
||||
final List<String> names = Functional.map(variables, Pair::first);
|
||||
final List<C> 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
211
java/expression/ListExpression.java
Normal file
211
java/expression/ListExpression.java
Normal file
@@ -0,0 +1,211 @@
|
||||
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;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
@SuppressWarnings("ClassReferencesSubclass")
|
||||
@FunctionalInterface
|
||||
public interface ListExpression extends ToMiniString {
|
||||
int evaluate(List<Integer> variables);
|
||||
|
||||
// Tests follow. You may temporarily remove everything til the end.
|
||||
|
||||
Add EXAMPLE = new Add(
|
||||
new Subtract(new Variable(0), new Const(1)),
|
||||
new Multiply(new Variable(1), new Const(10))
|
||||
);
|
||||
|
||||
Type<Integer> TYPE = new Type<>(a -> a, ExtendedRandom::nextInt, int.class);
|
||||
ExpressionKind<ListExpression, Integer> KIND = new ExpressionKind<>(
|
||||
TYPE,
|
||||
ListExpression.class,
|
||||
(r, c) -> IntStream.range(0, c)
|
||||
.mapToObj(name -> Pair.<String, ListExpression>of("$" + name, new Variable(name)))
|
||||
.toList(),
|
||||
(expr, variables, values) -> expr.evaluate(values)
|
||||
);
|
||||
|
||||
private static Const c(final Integer c) {
|
||||
return TYPE.constant(c);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"PointlessArithmeticExpression", "Convert2MethodRef"})
|
||||
static ExpressionTester<?, ?> tester(final TestCounter counter) {
|
||||
Asserts.assertEquals("Example toString()", "(($0 - 1) + ($1 * 10))", EXAMPLE.toString());
|
||||
Asserts.assertEquals(EXAMPLE + " at (2, 3)", 31, EXAMPLE.evaluate(List.of(2, 3)));
|
||||
|
||||
final Variable vx = new Variable(0);
|
||||
return new ExpressionTester<>(
|
||||
counter, KIND, c -> vars -> c,
|
||||
(op, a, b) -> vars -> op.apply(a.evaluate(vars), b.evaluate(vars)),
|
||||
(a, b) -> a + b, (a, b) -> a - b, (a, b) -> a * b, (a, b) -> a / b
|
||||
)
|
||||
.basic("10", "10", vars -> 10, c(10))
|
||||
.basic("$x", "$x", ListExpression::x, vx)
|
||||
.basic("($x + 2)", "$x + 2", vars -> x(vars) + 2, new Add(vx, c(2)))
|
||||
.basic("(2 - $x)", "2 - $x", vars -> 2 - x(vars), new Subtract(c(2), vx))
|
||||
.basic("(3 * $x)", "3 * $x", vars -> 3 * x(vars), new Multiply(c(3), vx))
|
||||
.basic("($x + $x)", "$x + $x", vars -> x(vars) + x(vars), new Add(vx, vx))
|
||||
.basic("($x / -2)", "$x / -2", vars -> -x(vars) / 2, new Divide(vx, c(-2)))
|
||||
.basic("(2 + $x)", "2 + $x", vars -> 2 + x(vars), new Add(c(2), vx))
|
||||
.basic("((1 + 2) + 3)", "1 + 2 + 3", vars -> 6, new Add(new Add(c(1), c(2)), c(3)))
|
||||
.basic("(1 + (2 + 3))", "1 + 2 + 3", vars -> 6, new Add(c(1), new Add(c(2), c(3))))
|
||||
.basic("((1 - 2) - 3)", "1 - 2 - 3", vars -> -4, new Subtract(new Subtract(c(1), c(2)), c(3)))
|
||||
.basic("(1 - (2 - 3))", "1 - (2 - 3)", vars -> 2, new Subtract(c(1), new Subtract(c(2), c(3))))
|
||||
.basic("((1 * 2) * 3)", "1 * 2 * 3", vars -> 6, new Multiply(new Multiply(c(1), c(2)), c(3)))
|
||||
.basic("(1 * (2 * 3))", "1 * 2 * 3", vars -> 6, new Multiply(c(1), new Multiply(c(2), c(3))))
|
||||
.basic("((10 / 2) / 3)", "10 / 2 / 3", vars -> 10 / 2 / 3, new Divide(new Divide(c(10), c(2)), c(3)))
|
||||
.basic("(10 / (3 / 2))", "10 / (3 / 2)", vars -> 10 / (3 / 2), new Divide(c(10), new Divide(c(3), c(2))))
|
||||
.basic("(10 * (3 / 2))", "10 * (3 / 2)", vars -> 10 * (3 / 2), new Multiply(c(10), new Divide(c(3), c(2))))
|
||||
.basic("(10 + (3 - 2))", "10 + 3 - 2", vars -> 10 + (3 - 2), new Add(c(10), new Subtract(c(3), c(2))))
|
||||
.basic(
|
||||
"(($x * $x) + (($x - 1) / 10))",
|
||||
"$x * $x + ($x - 1) / 10",
|
||||
vars -> x(vars) * x(vars) + (x(vars) - 1) / 10,
|
||||
new Add(new Multiply(vx, vx), new Divide(new Subtract(vx, c(1)), c(10)))
|
||||
)
|
||||
.basic(
|
||||
"($x * -1000000000)",
|
||||
"$x * -1000000000",
|
||||
vars -> x(vars) * -1_000_000_000,
|
||||
new Multiply(vx, c(-1_000_000_000))
|
||||
)
|
||||
.basic("(10 / $x)", "10 / $x", vars -> 10 / x(vars), new Divide(c(10), vx))
|
||||
.basic("($x / $x)", "$x / $x", vars -> x(vars) / x(vars), new Divide(vx, vx))
|
||||
|
||||
.advanced("(2 + 1)", "2 + 1", vars -> 2 + 1, new Add(c(2), c(1)))
|
||||
.advanced("($x - 1)", "$x - 1", vars -> x(vars) - 1, new Subtract(vx, c(1)))
|
||||
.advanced("(1 * 2)", "1 * 2", vars -> 1 * 2, new Multiply(c(1), c(2)))
|
||||
.advanced("($x / 1)", "$x / 1", vars -> x(vars) / 1, new Divide(vx, c(1)))
|
||||
.advanced("(1 + (2 + 1))", "1 + 2 + 1", vars -> 1 + 2 + 1, new Add(c(1), new Add(c(2), c(1))))
|
||||
.advanced(
|
||||
"($x - ($x - 1))",
|
||||
"$x - ($x - 1)",
|
||||
vars -> x(vars) - (x(vars) - 1),
|
||||
new Subtract(vx, new Subtract(vx, c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"(2 * ($x / 1))",
|
||||
"2 * ($x / 1)",
|
||||
vars -> 2 * (x(vars) / 1),
|
||||
new Multiply(c(2), new Divide(vx, c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"(2 / ($x - 1))",
|
||||
"2 / ($x - 1)",
|
||||
vars -> 2 / (x(vars) - 1),
|
||||
new Divide(c(2), new Subtract(vx, c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"((1 * 2) + $x)",
|
||||
"1 * 2 + $x",
|
||||
vars -> 1 * 2 + x(vars),
|
||||
new Add(new Multiply(c(1), c(2)), vx)
|
||||
)
|
||||
.advanced(
|
||||
"(($x - 1) - 2)",
|
||||
"$x - 1 - 2",
|
||||
vars -> x(vars) - 1 - 2,
|
||||
new Subtract(new Subtract(vx, c(1)), c(2))
|
||||
)
|
||||
.advanced(
|
||||
"(($x / 1) * 2)",
|
||||
"$x / 1 * 2",
|
||||
vars -> x(vars) / 1 * 2,
|
||||
new Multiply(new Divide(vx, c(1)), c(2))
|
||||
)
|
||||
.advanced("((2 + 1) / 1)", "(2 + 1) / 1", vars -> (2 + 1) / 1, new Divide(new Add(c(2), c(1)), c(1)))
|
||||
.advanced(
|
||||
"(1 + (1 + (2 + 1)))",
|
||||
"1 + 1 + 2 + 1",
|
||||
vars -> 1 + 1 + 2 + 1,
|
||||
new Add(c(1), new Add(c(1), new Add(c(2), c(1))))
|
||||
)
|
||||
.advanced(
|
||||
"($x - ((1 * 2) + $x))",
|
||||
"$x - (1 * 2 + $x)",
|
||||
vars -> x(vars) - (1 * 2 + x(vars)),
|
||||
new Subtract(vx, new Add(new Multiply(c(1), c(2)), vx))
|
||||
)
|
||||
.advanced(
|
||||
"($x * (2 / ($x - 1)))",
|
||||
"$x * (2 / ($x - 1))",
|
||||
vars -> x(vars) * (2 / (x(vars) - 1)),
|
||||
new Multiply(vx, new Divide(c(2), new Subtract(vx, c(1))))
|
||||
)
|
||||
.advanced(
|
||||
"($x / (1 + (2 + 1)))",
|
||||
"$x / (1 + 2 + 1)",
|
||||
vars -> x(vars) / (1 + 2 + 1),
|
||||
new Divide(vx, new Add(c(1), new Add(c(2), c(1))))
|
||||
)
|
||||
.advanced(
|
||||
"((1 * 2) + (2 + 1))",
|
||||
"1 * 2 + 2 + 1",
|
||||
vars -> 1 * 2 + 2 + 1,
|
||||
new Add(new Multiply(c(1), c(2)), new Add(c(2), c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"((2 + 1) - (2 + 1))",
|
||||
"2 + 1 - (2 + 1)",
|
||||
vars -> 2 + 1 - (2 + 1),
|
||||
new Subtract(new Add(c(2), c(1)), new Add(c(2), c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"(($x - 1) * ($x / 1))",
|
||||
"($x - 1) * ($x / 1)",
|
||||
vars -> (x(vars) - 1) * (x(vars) / 1),
|
||||
new Multiply(new Subtract(vx, c(1)), new Divide(vx, c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"(($x - 1) / (1 * 2))",
|
||||
"($x - 1) / (1 * 2)",
|
||||
vars -> (x(vars) - 1) / (1 * 2),
|
||||
new Divide(new Subtract(vx, c(1)), new Multiply(c(1), c(2)))
|
||||
)
|
||||
.advanced(
|
||||
"((($x - 1) - 2) + $x)",
|
||||
"$x - 1 - 2 + $x",
|
||||
vars -> x(vars) - 1 - 2 + x(vars),
|
||||
new Add(new Subtract(new Subtract(vx, c(1)), c(2)), vx)
|
||||
)
|
||||
.advanced(
|
||||
"(((1 * 2) + $x) - 1)",
|
||||
"1 * 2 + $x - 1",
|
||||
vars -> 1 * 2 + x(vars) - 1,
|
||||
new Subtract(new Add(new Multiply(c(1), c(2)), vx), c(1))
|
||||
)
|
||||
.advanced(
|
||||
"(((2 + 1) / 1) * $x)",
|
||||
"(2 + 1) / 1 * $x",
|
||||
vars -> (2 + 1) / 1 * x(vars),
|
||||
new Multiply(new Divide(new Add(c(2), c(1)), c(1)), vx)
|
||||
)
|
||||
.advanced(
|
||||
"((2 / ($x - 1)) / 2)",
|
||||
"2 / ($x - 1) / 2",
|
||||
vars -> 2 / (x(vars) - 1) / 2,
|
||||
new Divide(new Divide(c(2), new Subtract(vx, c(1))), c(2))
|
||||
);
|
||||
}
|
||||
|
||||
private static Integer x(final List<Integer> vars) {
|
||||
return vars.get(0);
|
||||
}
|
||||
|
||||
static void main(final String... args) {
|
||||
TripleExpression.SELECTOR
|
||||
.variant("List", ExpressionTest.v(ListExpression::tester))
|
||||
.main(args);
|
||||
}
|
||||
}
|
||||
10
java/expression/ToMiniString.java
Normal file
10
java/expression/ToMiniString.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package expression;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public interface ToMiniString {
|
||||
default String toMiniString() {
|
||||
return toString();
|
||||
}
|
||||
}
|
||||
190
java/expression/TripleExpression.java
Normal file
190
java/expression/TripleExpression.java
Normal file
@@ -0,0 +1,190 @@
|
||||
package expression;
|
||||
|
||||
import base.ExtendedRandom;
|
||||
import base.Pair;
|
||||
import base.Selector;
|
||||
import base.TestCounter;
|
||||
import expression.common.ExpressionKind;
|
||||
import expression.common.Type;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Three-argument arithmetic expression over integers.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@SuppressWarnings("ClassReferencesSubclass")
|
||||
public interface TripleExpression extends ToMiniString {
|
||||
int evaluate(int x, int y, int z);
|
||||
|
||||
// Tests follow. You may temporarily remove everything til the end.
|
||||
|
||||
Type<Integer> TYPE = new Type<>(a -> a, ExtendedRandom::nextInt, int.class);
|
||||
ExpressionKind<TripleExpression, Integer> KIND = new ExpressionKind<>(
|
||||
TYPE,
|
||||
TripleExpression.class,
|
||||
List.of(
|
||||
Pair.of("x", new Variable("x")),
|
||||
Pair.of("y", new Variable("y")),
|
||||
Pair.of("z", new Variable("z"))
|
||||
),
|
||||
(expr, variables, values) -> expr.evaluate(values.get(0), values.get(1), values.get(2))
|
||||
);
|
||||
|
||||
@SuppressWarnings("PointlessArithmeticExpression")
|
||||
static ExpressionTester<?, ?> tester(final TestCounter counter) {
|
||||
final Variable vx = new Variable("x");
|
||||
final Variable vy = new Variable("y");
|
||||
final Variable vz = new Variable("z");
|
||||
|
||||
return new ExpressionTester<>(
|
||||
counter, KIND, c -> (x, y, z) -> c,
|
||||
(op, a, b) -> (x, y, z) -> op.apply(a.evaluate(x, y, z), b.evaluate(x, y, z)),
|
||||
Integer::sum, (a, b) -> a - b, (a, b) -> a * b, (a, b) -> a / b
|
||||
)
|
||||
.basic("10", "10", (x, y, z) -> 10, c(10))
|
||||
.basic("x", "x", (x, y, z) -> x, vx)
|
||||
.basic("y", "y", (x, y, z) -> y, vy)
|
||||
.basic("z", "z", (x, y, z) -> z, vz)
|
||||
.basic("(x + 2)", "x + 2", (x, y, z) -> x + 2, new Add(vx, c(2)))
|
||||
.basic("(2 - y)", "2 - y", (x, y, z) -> 2 - y, new Subtract(c(2), vy))
|
||||
.basic("(3 * z)", "3 * z", (x, y, z) -> 3 * z, new Multiply(c(3), vz))
|
||||
.basic("(x / -2)", "x / -2", (x, y, z) -> -x / 2, new Divide(vx, c(-2)))
|
||||
.basic("((1 + 2) + 3)", "1 + 2 + 3", (x, y, z) -> 6, new Add(new Add(c(1), c(2)), c(3)))
|
||||
.basic("(1 + (2 + 3))", "1 + 2 + 3", (x, y, z) -> 6, new Add(c(1), new Add(c(2), c(3))))
|
||||
.basic("((1 - 2) - 3)", "1 - 2 - 3", (x, y, z) -> -4, new Subtract(new Subtract(c(1), c(2)), c(3)))
|
||||
.basic("(1 - (2 - 3))", "1 - (2 - 3)", (x, y, z) -> 2, new Subtract(c(1), new Subtract(c(2), c(3))))
|
||||
.basic("((1 * 2) * 3)", "1 * 2 * 3", (x, y, z) -> 6, new Multiply(new Multiply(c(1), c(2)), c(3)))
|
||||
.basic("(1 * (2 * 3))", "1 * 2 * 3", (x, y, z) -> 6, new Multiply(c(1), new Multiply(c(2), c(3))))
|
||||
.basic("((10 / 2) / 3)", "10 / 2 / 3", (x, y, z) -> 10 / 2 / 3, new Divide(new Divide(c(10), c(2)), c(3)))
|
||||
.basic("(10 / (3 / 2))", "10 / (3 / 2)", (x, y, z) -> 10, new Divide(c(10), new Divide(c(3), c(2))))
|
||||
.basic("((x * y) + ((z - 1) / 10))", "x * y + (z - 1) / 10", (x, y, z) -> x * y + (z - 1) / 10, new Add(
|
||||
new Multiply(vx, vy),
|
||||
new Divide(new Subtract(vz, c(1)), c(10))
|
||||
))
|
||||
.basic("(x + y)", "x + y", (x, y, z) -> x + y, new Add(vx, vy))
|
||||
.basic("(y + x)", "y + x", (x, y, z) -> y + x, new Add(vy, vx))
|
||||
|
||||
.advanced("(1 + 1)", "1 + 1", (x, y, z) -> 1 + 1, new Add(c(1), c(1)))
|
||||
.advanced("(y - x)", "y - x", (x, y, z) -> y - x, new Subtract(vy, vx))
|
||||
.advanced("(2 * x)", "2 * x", (x, y, z) -> 2 * x, new Multiply(c(2), vx))
|
||||
.advanced("(2 / x)", "2 / x", (x, y, z) -> 2 / x, new Divide(c(2), vx))
|
||||
.advanced("(z + (1 + 1))", "z + 1 + 1", (x, y, z) -> z + 1 + 1, new Add(vz, new Add(c(1), c(1))))
|
||||
.advanced(
|
||||
"(2 - (y - x))",
|
||||
"2 - (y - x)",
|
||||
(x, y, z) -> 2 - (y - x),
|
||||
new Subtract(c(2), new Subtract(vy, vx))
|
||||
)
|
||||
.advanced(
|
||||
"(z * (2 / x))",
|
||||
"z * (2 / x)",
|
||||
(x, y, z) -> z * (2 / x),
|
||||
new Multiply(vz, new Divide(c(2), vx))
|
||||
)
|
||||
.advanced(
|
||||
"(z / (y - x))",
|
||||
"z / (y - x)",
|
||||
(x, y, z) -> z / (y - x),
|
||||
new Divide(vz, new Subtract(vy, vx))
|
||||
)
|
||||
.advanced("((2 * x) + y)", "2 * x + y", (x, y, z) -> 2 * x + y, new Add(new Multiply(c(2), vx), vy))
|
||||
.advanced(
|
||||
"((y - x) - 2)",
|
||||
"y - x - 2",
|
||||
(x, y, z) -> y - x - 2,
|
||||
new Subtract(new Subtract(vy, vx), c(2))
|
||||
)
|
||||
.advanced("((2 / x) * y)", "2 / x * y", (x, y, z) -> 2 / x * y, new Multiply(new Divide(c(2), vx), vy))
|
||||
.advanced("((1 + 1) / x)", "(1 + 1) / x", (x, y, z) -> (1 + 1) / x, new Divide(new Add(c(1), c(1)), vx))
|
||||
.advanced("(1 + (2 * 3))", "1 + 2 * 3", (x, y, z) -> 7, new Add(c(1), new Multiply(c(2), c(3))))
|
||||
.advanced("(1 - (2 * 3))", "1 - 2 * 3", (x, y, z) -> -5, new Subtract(c(1), new Multiply(c(2), c(3))))
|
||||
.advanced("(1 + (2 / 3))", "1 + 2 / 3", (x, y, z) -> 1, new Add(c(1), new Divide(c(2), c(3))))
|
||||
.advanced("(1 - (2 / 3))", "1 - 2 / 3", (x, y, z) -> 1, new Subtract(c(1), new Divide(c(2), c(3))))
|
||||
.advanced(
|
||||
"(2 + (z + (1 + 1)))",
|
||||
"2 + z + 1 + 1",
|
||||
(x, y, z) -> 2 + z + 1 + 1,
|
||||
new Add(c(2), new Add(vz, new Add(c(1), c(1))))
|
||||
)
|
||||
.advanced(
|
||||
"(1 - ((2 * x) + y))",
|
||||
"1 - (2 * x + y)",
|
||||
(x, y, z) -> 1 - (2 * x + y),
|
||||
new Subtract(c(1), new Add(new Multiply(c(2), vx), vy))
|
||||
)
|
||||
.advanced(
|
||||
"(1 * (z / (y - x)))",
|
||||
"1 * (z / (y - x))",
|
||||
(x, y, z) -> 1 * (z / (y - x)),
|
||||
new Multiply(c(1), new Divide(vz, new Subtract(vy, vx)))
|
||||
)
|
||||
.advanced(
|
||||
"(z / (z + (1 + 1)))",
|
||||
"z / (z + 1 + 1)",
|
||||
(x, y, z) -> z / (z + 1 + 1),
|
||||
new Divide(vz, new Add(vz, new Add(c(1), c(1))))
|
||||
)
|
||||
.advanced(
|
||||
"((2 * x) + (1 + 1))",
|
||||
"2 * x + 1 + 1",
|
||||
(x, y, z) -> 2 * x + 1 + 1,
|
||||
new Add(new Multiply(c(2), vx), new Add(c(1), c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"((1 + 1) - (1 + 1))",
|
||||
"1 + 1 - (1 + 1)",
|
||||
(x, y, z) -> 1 + 1 - (1 + 1),
|
||||
new Subtract(new Add(c(1), c(1)), new Add(c(1), c(1)))
|
||||
)
|
||||
.advanced(
|
||||
"((y - x) * (2 / x))",
|
||||
"(y - x) * (2 / x)",
|
||||
(x, y, z) -> (y - x) * (2 / x),
|
||||
new Multiply(new Subtract(vy, vx), new Divide(c(2), vx))
|
||||
)
|
||||
.advanced(
|
||||
"((y - x) / (2 * x))",
|
||||
"(y - x) / (2 * x)",
|
||||
(x, y, z) -> (y - x) / (2 * x),
|
||||
new Divide(new Subtract(vy, vx), new Multiply(c(2), vx))
|
||||
)
|
||||
.advanced(
|
||||
"(((y - x) - 2) + 1)",
|
||||
"y - x - 2 + 1",
|
||||
(x, y, z) -> y - x - 2 + 1,
|
||||
new Add(new Subtract(new Subtract(vy, vx), c(2)), c(1))
|
||||
)
|
||||
.advanced(
|
||||
"(((2 * x) + y) - z)",
|
||||
"2 * x + y - z",
|
||||
(x, y, z) -> 2 * x + y - z,
|
||||
new Subtract(new Add(new Multiply(c(2), vx), vy), vz)
|
||||
)
|
||||
.advanced(
|
||||
"(((1 + 1) / x) * 2)",
|
||||
"(1 + 1) / x * 2",
|
||||
(x, y, z) -> (1 + 1) / x * 2,
|
||||
new Multiply(new Divide(new Add(c(1), c(1)), vx), c(2))
|
||||
)
|
||||
.advanced(
|
||||
"((z / (y - x)) / x)",
|
||||
"z / (y - x) / x",
|
||||
(x, y, z) -> z / (y - x) / x,
|
||||
new Divide(new Divide(vz, new Subtract(vy, vx)), vx)
|
||||
);
|
||||
}
|
||||
|
||||
private static Const c(final Integer c) {
|
||||
return TYPE.constant(c);
|
||||
}
|
||||
|
||||
Selector SELECTOR = ExpressionTest.SELECTOR
|
||||
.variant("Triple", ExpressionTest.v(TripleExpression::tester));
|
||||
|
||||
static void main(final String... args) {
|
||||
TripleExpression.SELECTOR.main(args);
|
||||
}
|
||||
}
|
||||
32
java/expression/common/Expr.java
Normal file
32
java/expression/common/Expr.java
Normal file
@@ -0,0 +1,32 @@
|
||||
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<C, V>(Node<C> node, List<Pair<String, V>> variables) {
|
||||
public <T> List<Pair<String, T>> variables(final BiFunction<String, V, T> f) {
|
||||
return Functional.map(
|
||||
variables,
|
||||
variable -> variable.second(f.apply(variable.first(), variable.second()))
|
||||
);
|
||||
}
|
||||
|
||||
public <T> Expr<C, T> convert(final BiFunction<String, V, T> f) {
|
||||
return of(node, variables(f));
|
||||
}
|
||||
|
||||
public Expr<C, V> node(final Function<Node<C>, Node<C>> f) {
|
||||
return of(f.apply(node), variables);
|
||||
}
|
||||
|
||||
public static <C, V> Expr<C, V> of(final Node<C> node, final List<Pair<String, V>> variables) {
|
||||
return new Expr<>(node, variables);
|
||||
}
|
||||
}
|
||||
94
java/expression/common/ExpressionKind.java
Normal file
94
java/expression/common/ExpressionKind.java
Normal file
@@ -0,0 +1,94 @@
|
||||
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<E extends ToMiniString, C> {
|
||||
private final Type<C> type;
|
||||
private final Class<E> kind;
|
||||
private final Variables<E> variables;
|
||||
private final Evaluator<E, C> evaluator;
|
||||
|
||||
public ExpressionKind(
|
||||
final Type<C> type,
|
||||
final Class<E> kind,
|
||||
final Variables<E> variables,
|
||||
final Evaluator<E, C> evaluator
|
||||
) {
|
||||
this.type = type;
|
||||
this.kind = kind;
|
||||
this.variables = variables;
|
||||
this.evaluator = evaluator;
|
||||
}
|
||||
|
||||
public ExpressionKind(
|
||||
final Type<C> type,
|
||||
final Class<E> kind,
|
||||
final List<Pair<String, E>> variables,
|
||||
final Evaluator<E, C> evaluator
|
||||
) {
|
||||
this(type, kind, (r, c) -> variables, evaluator);
|
||||
}
|
||||
|
||||
public C evaluate(final E expression, final List<String> variables, final List<C> 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<List<C>> allValues(final int length, final List<Integer> values) {
|
||||
return Functional.allValues(fromInts(values), length);
|
||||
}
|
||||
|
||||
public List<C> fromInts(final List<Integer> 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<E, C> withVariables(final Variables<E> variables) {
|
||||
return new ExpressionKind<>(type, kind, variables, evaluator);
|
||||
}
|
||||
|
||||
public Variables<E> variables() {
|
||||
return variables;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Variables<E> {
|
||||
List<Pair<String, E>> generate(final ExtendedRandom random, final int count);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Evaluator<E, R> {
|
||||
R evaluate(final E expression, final List<String> vars, final List<R> values) throws Exception;
|
||||
}
|
||||
}
|
||||
173
java/expression/common/Generator.java
Normal file
173
java/expression/common/Generator.java
Normal file
@@ -0,0 +1,173 @@
|
||||
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<C, E> {
|
||||
private final Supplier<C> constant;
|
||||
private final List<Named<Integer>> ops;
|
||||
private final ExpressionKind.Variables<E> variables;
|
||||
private final Set<String> forbidden;
|
||||
private final ExtendedRandom random;
|
||||
private final List<Function<List<Node<C>>, Stream<Node<C>>>> basicTests;
|
||||
|
||||
public Generator(
|
||||
final Supplier<C> constant,
|
||||
final List<Named<Integer>> ops,
|
||||
final ExpressionKind.Variables<E> variables,
|
||||
final Set<String> forbidden,
|
||||
final ExtendedRandom random,
|
||||
final List<Function<List<Node<C>>, Stream<Node<C>>>> 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 <C> Builder<C> builder(final Supplier<C> constant, final ExtendedRandom random) {
|
||||
return new Builder<>(random, constant);
|
||||
}
|
||||
|
||||
public void testRandom(
|
||||
final TestCounter counter,
|
||||
final int denominator,
|
||||
final Consumer<Expr<C, E>> 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<Expr<C, E>> consumer,
|
||||
final int seq,
|
||||
final int levels,
|
||||
final int perLevel,
|
||||
final BiFunction<List<Node<C>>, Integer, Node<C>> 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<Pair<String, E>> 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<C> generate(
|
||||
final List<Node<C>> variables,
|
||||
final boolean nullary,
|
||||
final Supplier<Node<C>> unary,
|
||||
final Supplier<Pair<Node<C>, Node<C>>> binary
|
||||
) {
|
||||
if (nullary || ops.isEmpty()) {
|
||||
return random.nextBoolean() ? random.randomItem(variables) : Node.constant(constant.get());
|
||||
} else {
|
||||
final Named<Integer> 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<C>, Node<C>> pair = binary.get();
|
||||
return Node.op(op.name(), pair.first(), pair.second());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Node<C> generate(final List<Node<C>> variables, final boolean nullary, final Supplier<Node<C>> child) {
|
||||
return generate(variables, nullary, child, () -> Pair.of(child.get(), child.get()));
|
||||
}
|
||||
|
||||
private Node<C> generateFullDepth(final List<Node<C>> variables, final int depth) {
|
||||
return generate(variables, depth == 0, () -> generateFullDepth(variables, depth - 1));
|
||||
}
|
||||
|
||||
private Node<C> generatePartialDepth(final List<Node<C>> variables, final int depth) {
|
||||
return generate(variables, depth == 0, () -> generatePartialDepth(variables, random.nextInt(depth)));
|
||||
}
|
||||
|
||||
private Node<C> generateSize(final List<Node<C>> 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<Expr<C, E>> consumer) {
|
||||
basicTests.forEach(test -> {
|
||||
final List<Pair<String, E>> 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<Pair<String, E>> variables(final int count) {
|
||||
List<Pair<String, E>> 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<C> {
|
||||
private final ExtendedRandom random;
|
||||
private final Supplier<C> constant;
|
||||
|
||||
private final List<Named<Integer>> ops = new ArrayList<>();
|
||||
private final Set<String> forbidden = new HashSet<>();
|
||||
|
||||
private Builder(final ExtendedRandom random, final Supplier<C> 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 <E> Generator<C, E> build(
|
||||
final ExpressionKind.Variables<E> variables,
|
||||
final List<Function<List<Node<C>>, Stream<Node<C>>>> basicTests
|
||||
) {
|
||||
return new Generator<>(constant, ops, variables, forbidden, random, basicTests);
|
||||
}
|
||||
}
|
||||
}
|
||||
106
java/expression/common/Node.java
Normal file
106
java/expression/common/Node.java
Normal file
@@ -0,0 +1,106 @@
|
||||
package expression.common;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public abstract class Node<T> {
|
||||
private Node() {
|
||||
}
|
||||
|
||||
public abstract <R> R get(Const<T, R> con, Nullary<R> nul, Unary<Node<T>, R> un, Binary<Node<T>, R> bin);
|
||||
public abstract <R> R cata(Const<T, R> con, Nullary<R> nul, Unary<R, R> un, Binary<R, R> 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 <T> Node<T> constant(final T value) {
|
||||
return new Node<>() {
|
||||
@Override
|
||||
public <R> R get(final Const<T, R> con, final Nullary<R> nul, final Unary<Node<T>, R> un, final Binary<Node<T>, R> bin) {
|
||||
return con.apply(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R> R cata(final Const<T, R> con, final Nullary<R> nul, final Unary<R, R> un, final Binary<R, R> bin) {
|
||||
return con.apply(value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Node<T> op(final String name) {
|
||||
return new Node<>() {
|
||||
@Override
|
||||
public <R> R get(final Const<T, R> con, final Nullary<R> nul, final Unary<Node<T>, R> un, final Binary<Node<T>, R> bin) {
|
||||
return nul.apply(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R> R cata(final Const<T, R> con, final Nullary<R> nul, final Unary<R, R> un, final Binary<R, R> bin) {
|
||||
return nul.apply(name);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Node<T> op(final String name, final int priority, final Node<T> arg) {
|
||||
return new Node<>() {
|
||||
@Override
|
||||
public <R> R get(final Const<T, R> con, final Nullary<R> nul, final Unary<Node<T>, R> un, final Binary<Node<T>, R> bin) {
|
||||
return un.apply(name, priority, arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R> R cata(final Const<T, R> con, final Nullary<R> nul, final Unary<R, R> un, final Binary<R, R> bin) {
|
||||
return un.apply(name, priority, arg.cata(con, nul, un, bin));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Node<T> op(final String name, final Node<T> arg1, final Node<T> arg2) {
|
||||
return new Node<>() {
|
||||
@Override
|
||||
public <R> R get(final Const<T, R> con, final Nullary<R> nul, final Unary<Node<T>, R> un, final Binary<Node<T>, R> bin) {
|
||||
return bin.apply(name, arg1, arg2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R> R cata(final Const<T, R> con, final Nullary<R> nul, final Unary<R, R> un, final Binary<R, R> bin) {
|
||||
return bin.apply(name, arg1.cata(con, nul, un, bin), arg2.cata(con, nul, un, bin));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Const<T, R> extends Function<T, R> {}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Nullary<R> extends Function<String, R> {}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Unary<T, R> {
|
||||
R apply(String name, int priority, T arg);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Binary<T, R> {
|
||||
R apply(String name, T arg1, T arg2);
|
||||
}
|
||||
}
|
||||
96
java/expression/common/NodeRenderer.java
Normal file
96
java/expression/common/NodeRenderer.java
Normal file
@@ -0,0 +1,96 @@
|
||||
package expression.common;
|
||||
|
||||
import base.ExtendedRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class NodeRenderer<C> {
|
||||
public static final String PAREN = "[";
|
||||
public static final List<Paren> 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<C, Settings, Node<C>> renderer;
|
||||
private final Map<String, String> brackets;
|
||||
private final ExtendedRandom random;
|
||||
|
||||
public NodeRenderer(
|
||||
final Renderer<C, Settings, Node<C>> renderer,
|
||||
final Map<String, String> brackets,
|
||||
final ExtendedRandom random
|
||||
) {
|
||||
this.renderer = renderer;
|
||||
this.brackets = Map.copyOf(brackets);
|
||||
this.random = random;
|
||||
}
|
||||
|
||||
public static <C> Node<C> paren(final boolean condition, final Node<C> 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<C> renderToNode(final Settings settings, final Expr<C, ?> expr) {
|
||||
final Expr<C, Node<C>> convert = expr.convert((name, variable) -> Node.op(name));
|
||||
return renderer.render(convert, settings);
|
||||
}
|
||||
|
||||
public String render(final Node<C> node, final List<Paren> 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<C, ?> 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<Paren> parens) {
|
||||
public Settings(final Mode mode, final int limit) {
|
||||
this(mode, limit, DEFAULT_PARENS);
|
||||
}
|
||||
|
||||
public <C> Node<C> extra(Node<C> node, final ExtendedRandom random) {
|
||||
while (random.nextInt(Integer.MAX_VALUE) < limit) {
|
||||
node = paren(true, node);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
public Settings withParens(final List<Paren> parens) {
|
||||
return this.parens.equals(parens) ? this : new Settings(mode, limit, List.copyOf(parens));
|
||||
}
|
||||
}
|
||||
}
|
||||
145
java/expression/common/NodeRendererBuilder.java
Normal file
145
java/expression/common/NodeRendererBuilder.java
Normal file
@@ -0,0 +1,145 @@
|
||||
package expression.common;
|
||||
|
||||
import base.ExtendedRandom;
|
||||
import base.Functional;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
|
||||
public class NodeRendererBuilder<C> {
|
||||
private final Renderer.Builder<C, NodeRenderer.Settings, Node<C>> nodeRenderer = Renderer.builder(Node::constant);
|
||||
private final Map<String, Priority> priorities = new HashMap<>();
|
||||
private final Map<String, String> brackets = new HashMap<>();
|
||||
private final ExtendedRandom random;
|
||||
|
||||
public NodeRendererBuilder(final ExtendedRandom random) {
|
||||
this.random = random;
|
||||
nodeRenderer.unary(NodeRenderer.PAREN, (mode, arg) -> NodeRenderer.paren(true, arg));
|
||||
}
|
||||
|
||||
public void unary(final String name, final int priority) {
|
||||
final String space = name.equals("-") || Character.isLetter(name.charAt(0)) ? " " : "";
|
||||
nodeRenderer.unary(
|
||||
name,
|
||||
(settings, arg) -> settings.extra(Node.op(name, priority, inner(settings, priority, arg, space)), random)
|
||||
);
|
||||
}
|
||||
|
||||
public void unary(final String left, final String right) {
|
||||
brackets.put(left, right);
|
||||
nodeRenderer.unary(
|
||||
left,
|
||||
(settings, arg) -> settings.extra(Node.op(left, Integer.MAX_VALUE, arg), random)
|
||||
);
|
||||
}
|
||||
|
||||
private Node<C> inner(final NodeRenderer.Settings settings, final int priority, final Node<C> arg, final String space) {
|
||||
if (settings.mode() == NodeRenderer.Mode.FULL) {
|
||||
return NodeRenderer.paren(true, arg);
|
||||
} else {
|
||||
final String op = arg.get(
|
||||
c -> space,
|
||||
n -> space,
|
||||
(n, p, a) ->
|
||||
priority > unaryPriority(arg) ? NodeRenderer.PAREN :
|
||||
NodeRenderer.PAREN.equals(n) ? "" :
|
||||
space,
|
||||
(n, a, b) -> NodeRenderer.PAREN
|
||||
);
|
||||
return op.isEmpty() ? arg : Node.op(op, Priority.MAX.priority | 1, arg);
|
||||
}
|
||||
}
|
||||
|
||||
private static <C> Integer unaryPriority(final Node<C> node) {
|
||||
return node.get(c -> Integer.MAX_VALUE, n -> Integer.MAX_VALUE, (n, p, a) -> p, (n, a, b) -> Integer.MIN_VALUE);
|
||||
}
|
||||
|
||||
public void binary(final String name, final int priority) {
|
||||
final Priority mp = new Priority(name, priority);
|
||||
priorities.put(name, mp);
|
||||
|
||||
nodeRenderer.binary(name, (settings, l, r) -> settings.extra(process(settings, mp, l, r), random));
|
||||
}
|
||||
|
||||
private Node<C> process(final NodeRenderer.Settings settings, final Priority mp, final Node<C> l, final Node<C> r) {
|
||||
if (settings.mode() == NodeRenderer.Mode.FULL) {
|
||||
return NodeRenderer.paren(true, op(mp, l, r));
|
||||
}
|
||||
|
||||
final Priority lp = priority(l);
|
||||
final Priority rp = priority(r);
|
||||
|
||||
final int rc = rp.compareLevels(mp);
|
||||
|
||||
// :NOTE: Especially ugly code, do not replicate
|
||||
final boolean advanced = settings.mode() == NodeRenderer.Mode.SAME
|
||||
|| mp.has(2)
|
||||
|| mp.has(1) && (mp != rp || (settings.mode() == NodeRenderer.Mode.TRUE_MINI && hasOther(r, rp)));
|
||||
|
||||
final Node<C> al = NodeRenderer.paren(lp.compareLevels(mp) < 0, l);
|
||||
if (rc == 0 && !advanced) {
|
||||
return get(r, null, (n, a, b) -> rp.op(mp.op(al, a), b));
|
||||
} else {
|
||||
return mp.op(al, NodeRenderer.paren(rc == 0 && advanced || rc < 0, r));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasOther(final Node<C> node, final Priority priority) {
|
||||
return get(node, () -> false, (name, l, r) -> {
|
||||
final Priority p = Functional.get(priorities, name);
|
||||
if (p.compareLevels(priority) != 0) {
|
||||
return false;
|
||||
}
|
||||
return p != priority || hasOther(l, priority);
|
||||
});
|
||||
}
|
||||
|
||||
private Node<C> op(final Priority mp, final Node<C> l, final Node<C> r) {
|
||||
return mp.op(l, r);
|
||||
}
|
||||
|
||||
private Priority priority(final Node<C> node) {
|
||||
return get(node, () -> Priority.MAX, (n, a, b) -> Functional.get(priorities, n));
|
||||
}
|
||||
|
||||
private <R> R get(final Node<C> node, final Supplier<R> common, final Node.Binary<Node<C>, R> binary) {
|
||||
return node.get(
|
||||
c -> common.get(),
|
||||
n -> common.get(),
|
||||
(n, p, a) -> common.get(),
|
||||
binary
|
||||
);
|
||||
}
|
||||
|
||||
public NodeRenderer<C> build() {
|
||||
return new NodeRenderer<>(nodeRenderer.build(), brackets, random);
|
||||
}
|
||||
|
||||
// :NOTE: Especially ugly bit-fiddling, do not replicate
|
||||
private record Priority(String op, int priority) {
|
||||
private static final int Q = 3;
|
||||
private static final Priority MAX = new Priority("MAX", Integer.MAX_VALUE - Q);
|
||||
|
||||
private int compareLevels(final Priority that) {
|
||||
return (priority | Q) - (that.priority | Q);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Priority(%s, %d, %d)", op, priority | Q, priority & Q);
|
||||
}
|
||||
|
||||
public <C> Node<C> op(final Node<C> l, final Node<C> r) {
|
||||
return Node.op(op, l, r);
|
||||
}
|
||||
|
||||
private boolean has(final int value) {
|
||||
return (priority & Q) == value;
|
||||
}
|
||||
}
|
||||
}
|
||||
60
java/expression/common/Reason.java
Normal file
60
java/expression/common/Reason.java
Normal file
@@ -0,0 +1,60 @@
|
||||
package expression.common;
|
||||
|
||||
import base.Either;
|
||||
|
||||
import java.util.function.LongUnaryOperator;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class Reason {
|
||||
public static final Reason OVERFLOW = new Reason("Overflow");
|
||||
public static final Reason DBZ = new Reason("Division by zero");
|
||||
|
||||
private final String description;
|
||||
|
||||
public Reason(final String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public static <T> Either<Reason, T> eval(final Supplier<T> action) {
|
||||
try {
|
||||
return Either.right(action.get());
|
||||
} catch (final ReasonException e) {
|
||||
return Either.left(e.reason);
|
||||
}
|
||||
}
|
||||
|
||||
public static int overflow(final long value) {
|
||||
return value < Integer.MIN_VALUE || Integer.MAX_VALUE < value
|
||||
? OVERFLOW.error()
|
||||
: (int) value;
|
||||
}
|
||||
|
||||
public <T> T error() {
|
||||
throw new ReasonException(this);
|
||||
}
|
||||
|
||||
public LongUnaryOperator less(final long limit, final LongUnaryOperator op) {
|
||||
return a -> a < limit ? error() : op.applyAsLong(a);
|
||||
}
|
||||
|
||||
public LongUnaryOperator greater(final int limit, final LongUnaryOperator op) {
|
||||
return a -> a > limit ? error() : op.applyAsLong(a);
|
||||
}
|
||||
|
||||
private static class ReasonException extends RuntimeException {
|
||||
private final Reason reason;
|
||||
|
||||
public ReasonException(final Reason reason) {
|
||||
super(reason.description);
|
||||
this.reason = reason;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Reason(%s)", description);
|
||||
}
|
||||
}
|
||||
60
java/expression/common/Renderer.java
Normal file
60
java/expression/common/Renderer.java
Normal file
@@ -0,0 +1,60 @@
|
||||
package expression.common;
|
||||
|
||||
import base.Functional;
|
||||
import base.Pair;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public interface Renderer<C, S, R> {
|
||||
static <C, S, R> Builder<C, S, R> builder(final Node.Const<C, R> constant) {
|
||||
return new Builder<>(constant);
|
||||
}
|
||||
|
||||
R render(final Expr<C, R> expr, final S settings);
|
||||
|
||||
@FunctionalInterface
|
||||
interface UnaryOperator<S, R> {
|
||||
R apply(S settings, R arg);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface BinaryOperator<S, R> {
|
||||
R apply(S settings, R arg1, R arg2);
|
||||
}
|
||||
|
||||
final class Builder<C, S, R> {
|
||||
private final Node.Const<C, R> constant;
|
||||
private final Map<String, UnaryOperator<S, R>> unary = new HashMap<>();
|
||||
private final Map<String, BinaryOperator<S, R>> binary = new HashMap<>();
|
||||
|
||||
private Builder(final Node.Const<C, R> constant) {
|
||||
this.constant = constant;
|
||||
}
|
||||
|
||||
public void unary(final String name, final UnaryOperator<S, R> op) {
|
||||
unary.put(name, op);
|
||||
}
|
||||
|
||||
public void binary(final String name, final BinaryOperator<S, R> op) {
|
||||
binary.put(name, op);
|
||||
}
|
||||
|
||||
public Renderer<C, S, R> build() {
|
||||
return (expr, settings) -> {
|
||||
final Map<String, R> vars = expr.variables().stream()
|
||||
.collect(Collectors.toMap(Pair::first, Pair::second));
|
||||
return expr.node().cata(
|
||||
constant,
|
||||
name -> Functional.get(vars, name),
|
||||
(name, p, arg) -> Functional.get(unary, name).apply(settings, arg),
|
||||
(name, arg1, arg2) -> Functional.get(binary, name).apply(settings, arg1, arg2)
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
56
java/expression/common/TestGenerator.java
Normal file
56
java/expression/common/TestGenerator.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package expression.common;
|
||||
|
||||
import base.Pair;
|
||||
import base.TestCounter;
|
||||
import expression.ToMiniString;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class TestGenerator<C, E extends ToMiniString> {
|
||||
private final Generator<C, E> generator;
|
||||
private final NodeRenderer<C> renderer;
|
||||
|
||||
public TestGenerator(final Generator<C, E> generator, final NodeRenderer<C> renderer) {
|
||||
this.generator = generator;
|
||||
this.renderer = renderer;
|
||||
}
|
||||
|
||||
public void testBasic(final Consumer<Test<C, E>> test) {
|
||||
generator.testBasic(consumer(test));
|
||||
}
|
||||
|
||||
public void testRandom(final TestCounter counter, final int denominator, final Consumer<Test<C, E>> test) {
|
||||
generator.testRandom(counter, denominator, consumer(test));
|
||||
}
|
||||
|
||||
private Consumer<Expr<C, E>> consumer(final Consumer<TestGenerator.Test<C, E>> consumer) {
|
||||
return expr -> consumer.accept(new TestGenerator.Test<>(expr, renderer));
|
||||
}
|
||||
|
||||
|
||||
public List<Pair<String, E>> variables(final int count) {
|
||||
return generator.variables(count);
|
||||
}
|
||||
|
||||
public String render(final Expr<C, ?> expr, final NodeRenderer.Settings settings) {
|
||||
return renderer.render(expr, settings);
|
||||
}
|
||||
|
||||
public static class Test<C, E> {
|
||||
public final Expr<C, E> expr;
|
||||
private final Map<NodeRenderer.Settings, String> rendered = new HashMap<>();
|
||||
private final NodeRenderer<C> renderer;
|
||||
|
||||
public Test(final Expr<C, E> expr, final NodeRenderer<C> renderer) {
|
||||
this.expr = expr;
|
||||
this.renderer = renderer;
|
||||
}
|
||||
|
||||
public String render(final NodeRenderer.Settings settings) {
|
||||
return rendered.computeIfAbsent(settings, s -> renderer.render(expr, s));
|
||||
}
|
||||
}
|
||||
}
|
||||
145
java/expression/common/TestGeneratorBuilder.java
Normal file
145
java/expression/common/TestGeneratorBuilder.java
Normal file
@@ -0,0 +1,145 @@
|
||||
package expression.common;
|
||||
|
||||
import base.ExtendedRandom;
|
||||
import base.Functional;
|
||||
import expression.ToMiniString;
|
||||
import expression.common.ExpressionKind.Variables;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class TestGeneratorBuilder<C> {
|
||||
private final ExtendedRandom random;
|
||||
|
||||
private final Generator.Builder<C> generator;
|
||||
private final NodeRendererBuilder<C> renderer;
|
||||
|
||||
private final List<Function<List<Node<C>>, Stream<Node<C>>>> basicTests = new ArrayList<>();
|
||||
private final List<Node<C>> consts;
|
||||
private final boolean verbose;
|
||||
|
||||
public TestGeneratorBuilder(
|
||||
final ExtendedRandom random,
|
||||
final Supplier<C> constant,
|
||||
final List<C> constants,
|
||||
final boolean verbose
|
||||
) {
|
||||
this.random = random;
|
||||
this.verbose = verbose;
|
||||
|
||||
generator = Generator.builder(constant, random);
|
||||
renderer = new NodeRendererBuilder<>(random);
|
||||
|
||||
consts = Functional.map(constants, Node::constant);
|
||||
basicTests.add(vars -> consts.stream());
|
||||
basicTests.add(List::stream);
|
||||
}
|
||||
|
||||
private Node<C> c() {
|
||||
return random.randomItem(consts);
|
||||
}
|
||||
|
||||
private Node<C> v(final List<Node<C>> variables) {
|
||||
return random.randomItem(variables);
|
||||
}
|
||||
|
||||
private static <C> Node<C> f(final String name, final int priority, final Node<C> arg) {
|
||||
return Node.op(name, priority, arg);
|
||||
}
|
||||
|
||||
private static <C> Node<C> f(final String left, final Node<C> arg) {
|
||||
return Node.op(left, Integer.MAX_VALUE, arg);
|
||||
}
|
||||
|
||||
private static <C> Node<C> f(final String name, final Node<C> arg1, final Node<C> arg2) {
|
||||
return Node.op(name, arg1, arg2);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
private void basicTests(final Function<List<Node<C>>, Node<C>>... tests) {
|
||||
Arrays.stream(tests).map(test -> test.andThen(Stream::of)).forEachOrdered(basicTests::add);
|
||||
}
|
||||
|
||||
public void unary(final String name, final int priority) {
|
||||
generator.add(name, (priority & 1) * 2 - 1);
|
||||
renderer.unary(name, priority);
|
||||
|
||||
if (verbose) {
|
||||
basicTests.add(vars -> Stream.concat(consts.stream(), vars.stream()).map(a -> f(name, priority, a)));
|
||||
} else {
|
||||
basicTests(vars -> f(name, priority, c()), vars -> f(name, priority, v(vars)));
|
||||
}
|
||||
|
||||
final Function<List<Node<C>>, Node<C>> p1 = vars -> f(name, priority, f(name, priority, f("+", v(vars), c())));
|
||||
final Function<List<Node<C>>, Node<C>> p2 = vars -> f("*", v(vars), f("*", v(vars), f(name, priority, c())));
|
||||
basicTests(
|
||||
vars -> f(name, priority, f("+", v(vars), v(vars))),
|
||||
vars -> f(name, priority, f(name, priority, v(vars))),
|
||||
vars -> f(name, priority, f("/", f(name, priority, v(vars)), f("+", v(vars), v(vars)))),
|
||||
p1,
|
||||
p2,
|
||||
vars -> f("+", p1.apply(vars), p2.apply(vars))
|
||||
);
|
||||
}
|
||||
|
||||
public void unary(final String left, final String right) {
|
||||
generator.add(left, 1);
|
||||
renderer.unary(left, right);
|
||||
|
||||
if (verbose) {
|
||||
basicTests.add(vars -> Stream.concat(consts.stream(), vars.stream()).map(a -> f(left, a)));
|
||||
} else {
|
||||
basicTests(vars -> f(left, c()), vars -> f(left, v(vars)));
|
||||
}
|
||||
|
||||
final Function<List<Node<C>>, Node<C>> p1 = vars -> f(left, f(left, f("+", v(vars), c())));
|
||||
final Function<List<Node<C>>, Node<C>> p2 = vars -> f("*", v(vars), f("*", v(vars), f(left, c())));
|
||||
basicTests(
|
||||
vars -> f(left, f("+", v(vars), v(vars))),
|
||||
vars -> f(left, f(left, v(vars))),
|
||||
vars -> f(left, f("/", f(left, v(vars)), f("+", v(vars), v(vars)))),
|
||||
p1,
|
||||
p2,
|
||||
vars -> f("+", p1.apply(vars), p2.apply(vars))
|
||||
);
|
||||
}
|
||||
|
||||
public void binary(final String name, final int priority) {
|
||||
generator.add(name, 2);
|
||||
renderer.binary(name, priority);
|
||||
|
||||
if (verbose) {
|
||||
basicTests.add(vars -> Stream.concat(consts.stream(), vars.stream().limit(3))
|
||||
.flatMap(a -> consts.stream().map(b -> f(name, a, b))));
|
||||
} else {
|
||||
basicTests(
|
||||
vars -> f(name, c(), c()),
|
||||
vars -> f(name, v(vars), c()),
|
||||
vars -> f(name, c(), v(vars)),
|
||||
vars -> f(name, v(vars), v(vars))
|
||||
);
|
||||
}
|
||||
|
||||
final Function<List<Node<C>>, Node<C>> p1 = vars -> f(name, f(name, f("+", v(vars), c()), v(vars)), v(vars));
|
||||
final Function<List<Node<C>>, Node<C>> p2 = vars -> f("*", v(vars), f("*", v(vars), f(name, c(), v(vars))));
|
||||
|
||||
basicTests(
|
||||
vars -> f(name, f(name, v(vars), v(vars)), v(vars)),
|
||||
vars -> f(name, v(vars), f(name, v(vars), v(vars))),
|
||||
vars -> f(name, f(name, v(vars), v(vars)), f(name, v(vars), v(vars))),
|
||||
vars -> f(name, f("-", f(name, v(vars), v(vars)), c()), f("+", v(vars), v(vars))),
|
||||
p1,
|
||||
p2,
|
||||
vars -> f("+", p1.apply(vars), p2.apply(vars))
|
||||
);
|
||||
}
|
||||
|
||||
public <E extends ToMiniString> TestGenerator<C,E> build(final Variables<E> variables) {
|
||||
return new TestGenerator<>(generator.build(variables, basicTests), renderer.build());
|
||||
}
|
||||
}
|
||||
51
java/expression/common/Type.java
Normal file
51
java/expression/common/Type.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package expression.common;
|
||||
|
||||
import base.Asserts;
|
||||
import base.ExtendedRandom;
|
||||
import expression.Const;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class Type<C> {
|
||||
private final IntFunction<C> fromInt;
|
||||
private final Function<ExtendedRandom, C> random;
|
||||
private final Function<C, Const> constant;
|
||||
|
||||
public Type(final IntFunction<C> fromInt, final Function<ExtendedRandom, C> random, final Class<?> type) {
|
||||
this.fromInt = fromInt;
|
||||
this.random = random;
|
||||
|
||||
try {
|
||||
final MethodHandle constructor = MethodHandles.publicLookup()
|
||||
.findConstructor(Const.class, MethodType.methodType(void.class, type));
|
||||
constant = c -> {
|
||||
try {
|
||||
return (Const) constructor.invoke(c);
|
||||
} catch (final Throwable e) {
|
||||
throw Asserts.error("Cannot create new Const(%s): %s", c, e);
|
||||
}
|
||||
};
|
||||
} catch (final IllegalAccessException | NoSuchMethodException e) {
|
||||
throw Asserts.error("Cannot find constructor Const(%s): %s", type, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Const constant(final C value) {
|
||||
return constant.apply(value);
|
||||
}
|
||||
|
||||
public C fromInt(final int value) {
|
||||
return fromInt.apply(value);
|
||||
}
|
||||
|
||||
public C randomValue(final ExtendedRandom random) {
|
||||
return this.random.apply(random);
|
||||
}
|
||||
}
|
||||
7
java/expression/common/package-info.java
Normal file
7
java/expression/common/package-info.java
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Expressions generators for expression-based homeworks
|
||||
* of <a href="https://www.kgeorgiy.info/courses/prog-intro/">Introduction to Programming</a> course.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
package expression.common;
|
||||
30
java/expression/exceptions/ExceptionsTest.java
Normal file
30
java/expression/exceptions/ExceptionsTest.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package expression.exceptions;
|
||||
|
||||
import base.Selector;
|
||||
import expression.ListExpression;
|
||||
import expression.parser.Operations;
|
||||
|
||||
import static expression.parser.Operations.*;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class ExceptionsTest {
|
||||
private static final ExpressionParser PARSER = new ExpressionParser();
|
||||
private static final Operations.Operation LIST = kind(ListExpression.KIND, PARSER::parse);
|
||||
|
||||
public static final Selector SELECTOR = Selector.composite(ExceptionsTest.class, ExceptionsTester::new, "easy", "hard")
|
||||
.variant("Base", LIST, ADD, SUBTRACT, MULTIPLY, DIVIDE, NEGATE)
|
||||
.variant("3637", POW, LOG)
|
||||
.variant("3839", POW, LOG, POW_2, LOG_2)
|
||||
.variant("3435", POW_2, LOG_2)
|
||||
.variant("3233", HIGH, LOW)
|
||||
.selector();
|
||||
|
||||
private ExceptionsTest() {
|
||||
}
|
||||
|
||||
public static void main(final String... args) {
|
||||
SELECTOR.main(args);
|
||||
}
|
||||
}
|
||||
162
java/expression/exceptions/ExceptionsTestSet.java
Normal file
162
java/expression/exceptions/ExceptionsTestSet.java
Normal file
@@ -0,0 +1,162 @@
|
||||
package expression.exceptions;
|
||||
|
||||
import base.Functional;
|
||||
import base.Named;
|
||||
import base.Pair;
|
||||
import expression.ToMiniString;
|
||||
import expression.Variable;
|
||||
import expression.common.ExpressionKind;
|
||||
import expression.parser.ParserTestSet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.LongBinaryOperator;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class ExceptionsTestSet<E extends ToMiniString, C> extends ParserTestSet<E, C> {
|
||||
private static final int D = 5;
|
||||
private static final List<Integer> OVERFLOW_VALUES = new ArrayList<>();
|
||||
private final char[] CHARS = "AZ+-*%()[]<>".toCharArray();
|
||||
|
||||
static {
|
||||
Functional.addRange(OVERFLOW_VALUES, D, Integer.MIN_VALUE + D);
|
||||
Functional.addRange(OVERFLOW_VALUES, D, Integer.MIN_VALUE / 2);
|
||||
Functional.addRange(OVERFLOW_VALUES, D, (int) -Math.sqrt(Integer.MAX_VALUE));
|
||||
Functional.addRange(OVERFLOW_VALUES, D, 0);
|
||||
Functional.addRange(OVERFLOW_VALUES, D, (int) Math.sqrt(Integer.MAX_VALUE));
|
||||
Functional.addRange(OVERFLOW_VALUES, D, Integer.MAX_VALUE / 2);
|
||||
Functional.addRange(OVERFLOW_VALUES, D, Integer.MAX_VALUE - D);
|
||||
}
|
||||
|
||||
private final List<Named<String>> parsingTest;
|
||||
|
||||
public ExceptionsTestSet(final ExceptionsTester tester, final ParsedKind<E, C> kind) {
|
||||
super(tester, kind, false);
|
||||
parsingTest = tester.parsingTest;
|
||||
}
|
||||
|
||||
private void testParsingErrors() {
|
||||
counter.testForEach(parsingTest, op -> {
|
||||
final List<String> names = Functional.map(kind.kind().variables().generate(counter.random(), 3), Pair::first);
|
||||
final String expr = mangle(op.value(), names);
|
||||
try {
|
||||
kind.parse(expr, names);
|
||||
counter.fail("Successfully parsed '%s'", op.value());
|
||||
} catch (final Exception e) {
|
||||
counter.format("%-30s %s%n", op.name(), e.getClass().getSimpleName() + ": " + e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void testOverflow() {
|
||||
final List<Pair<String, E>> variables = kind.kind().variables().generate(counter.random(), 3);
|
||||
final List<String> names = Functional.map(variables, Pair::first);
|
||||
final Variable vx = (Variable) variables.get(0).second();
|
||||
final Variable vy = (Variable) variables.get(1).second();
|
||||
|
||||
//noinspection Convert2MethodRef
|
||||
testOverflow(names, (a, b) -> a + b, "+", new CheckedAdd(vx, vy));
|
||||
testOverflow(names, (a, b) -> a - b, "-", new CheckedSubtract(vx, vy));
|
||||
testOverflow(names, (a, b) -> a * b, "*", new CheckedMultiply(vx, vy));
|
||||
testOverflow(names, (a, b) -> b == 0 ? Long.MAX_VALUE : a / b, "/", new CheckedDivide(vx, vy));
|
||||
testOverflow(names, (a, b) -> -b, "<- ignore first argument, unary -", new CheckedNegate(vy));
|
||||
}
|
||||
|
||||
private void testOverflow(final List<String> names, final LongBinaryOperator f, final String op, final Object expression) {
|
||||
final ExpressionKind<E, C> kind = this.kind.kind();
|
||||
for (final int a : OVERFLOW_VALUES) {
|
||||
for (final int b : OVERFLOW_VALUES) {
|
||||
final long expected = f.applyAsLong(a, b);
|
||||
final boolean isInt = Integer.MIN_VALUE <= expected && expected <= Integer.MAX_VALUE;
|
||||
try {
|
||||
final C actual = kind.evaluate(
|
||||
kind.cast(expression),
|
||||
names,
|
||||
kind.fromInts(List.of(a, b, 0))
|
||||
);
|
||||
counter.checkTrue(
|
||||
isInt && kind.fromInt((int) expected).equals(actual),
|
||||
"%d %s %d == %d", a, op, b, actual
|
||||
);
|
||||
} catch (final Exception e) {
|
||||
if (isInt) {
|
||||
counter.fail(e, "Unexpected error in %d %s %d", a, op, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void test() {
|
||||
counter.scope("Overflow tests", (Runnable) this::testOverflow);
|
||||
super.test();
|
||||
counter.scope("Parsing error tests", this::testParsingErrors);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected E parse(final String expression, final List<String> variables, final boolean reparse) {
|
||||
final String expr = expression.strip();
|
||||
if (expr.length() > 10) {
|
||||
for (final char ch : CHARS) {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
final int index = 1 + tester.random().nextInt(expr.length() - 2);
|
||||
int pi = index - 1;
|
||||
while (Character.isWhitespace(expr.charAt(pi))) {
|
||||
pi--;
|
||||
}
|
||||
int ni = index;
|
||||
while (Character.isWhitespace(expr.charAt(ni))) {
|
||||
ni++;
|
||||
}
|
||||
final char pc = expr.charAt(pi);
|
||||
final char nc = expr.charAt(ni);
|
||||
if (
|
||||
"-([{*∛√²³₂₃!‖⎵⎴⌊⌈=?".indexOf(nc) < 0 &&
|
||||
(!Character.isLetterOrDigit(pc) || !Character.isLetterOrDigit(ch)) &&
|
||||
nc != ch && pc != ch &&
|
||||
!Character.isLetterOrDigit(nc) && nc != '$'
|
||||
) {
|
||||
shouldFail(
|
||||
variables,
|
||||
"Parsing error expected for " + insert(expr, index, "<ERROR_INSERTED -->" + ch + "<-- ERROR_INSERTED>"),
|
||||
insert(expr, index, String.valueOf(ch))
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
parens(variables, expr, '[', ']');
|
||||
parens(variables, expr, '{', '}');
|
||||
}
|
||||
|
||||
return counter.testV(() -> counter.call("parse", () -> kind.parse(expr, variables)));
|
||||
}
|
||||
|
||||
private static String insert(final String expr, final int index, final String value) {
|
||||
return expr.substring(0, index) + value + expr.substring(index);
|
||||
}
|
||||
|
||||
private void parens(final List<String> variables, final String expr, final char open, final char close) {
|
||||
if (expr.indexOf(open) >= 0) {
|
||||
replaces(variables, expr, open, '(');
|
||||
replaces(variables, expr, close, ')');
|
||||
if (expr.indexOf('(') >= 0) {
|
||||
replaces(variables, expr, '(', open);
|
||||
replaces(variables, expr, ')', close);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void replaces(final List<String> variables, final String expr, final char what, final char by) {
|
||||
final String input = expr.replace(what, by);
|
||||
shouldFail(variables, "Unmatched parentheses: " + input, input);
|
||||
}
|
||||
|
||||
private void shouldFail(final List<String> variables, final String message, final String input) {
|
||||
counter.shouldFail(message, () -> kind.parse(input, variables));
|
||||
}
|
||||
}
|
||||
100
java/expression/exceptions/ExceptionsTester.java
Normal file
100
java/expression/exceptions/ExceptionsTester.java
Normal file
@@ -0,0 +1,100 @@
|
||||
package expression.exceptions;
|
||||
|
||||
import base.Named;
|
||||
import base.TestCounter;
|
||||
import expression.common.Reason;
|
||||
import expression.parser.ParserTestSet;
|
||||
import expression.parser.ParserTester;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.LongBinaryOperator;
|
||||
import java.util.function.LongToIntFunction;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class ExceptionsTester extends ParserTester {
|
||||
/* package-private */ final List<Named<String>> parsingTest = new ArrayList<>(List.of(
|
||||
Named.of("No first argument", "* $y * $z"),
|
||||
Named.of("No middle argument", "$x * * $z"),
|
||||
Named.of("No last argument", "$x * $y * "),
|
||||
Named.of("No first argument'", "1 + (* $y * $z) + 2"),
|
||||
Named.of("No middle argument'", "1 + ($x * / 9) + 3"),
|
||||
Named.of("No last argument'", "1 + ($x * $y - ) + 3"),
|
||||
Named.of("No opening parenthesis", "$x * $y)"),
|
||||
Named.of("No closing parenthesis", "($x * $y"),
|
||||
Named.of("Mismatched closing parenthesis", "($x * $y]"),
|
||||
Named.of("Mismatched open parenthesis", "[$x * $y)"),
|
||||
Named.of("Start symbol", "@$x * $y"),
|
||||
Named.of("Middle symbol", "$x @ * $y"),
|
||||
Named.of("End symbol", "$x * $y@"),
|
||||
Named.of("Constant overflow 1", Integer.MIN_VALUE - 1L + ""),
|
||||
Named.of("Constant overflow 2", Integer.MAX_VALUE + 1L + ""),
|
||||
Named.of("Bare +", "+"),
|
||||
Named.of("Bare -", "-"),
|
||||
Named.of("Bare a", "a"),
|
||||
Named.of("(())", "(())"),
|
||||
Named.of("Spaces in numbers", "10 20")
|
||||
));
|
||||
|
||||
public ExceptionsTester(final TestCounter counter) {
|
||||
super(counter);
|
||||
}
|
||||
|
||||
|
||||
private void parsingTests(final String... tests) {
|
||||
for (final String test : tests) {
|
||||
parsingTest.add(Named.of(test, test));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unary(final String name, final int priority, final BiFunction<Long, LongToIntFunction, Long> op) {
|
||||
if (allowed(name)) {
|
||||
parsingTests(name, "1 * " + name, name + " * 1");
|
||||
}
|
||||
parsingTests(name + "()", name + "(1, 2)");
|
||||
if (name.length() > 1) {
|
||||
parsingTests(name + "q");
|
||||
}
|
||||
if (allLetterAndDigit(name)) {
|
||||
parsingTests(name + "1", name + "q");
|
||||
}
|
||||
super.unary(name, priority, op);
|
||||
}
|
||||
|
||||
private static boolean allowed(final String name) {
|
||||
return !"xyz".contains(name.substring(0, 1)) && !"xyz".contains(name.substring(name.length() - 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binary(final String name, final int priority, final LongBinaryOperator op) {
|
||||
if (allowed(name)) {
|
||||
parsingTests(name);
|
||||
}
|
||||
parsingTests("1 " + name, "1 " + name + " * 3");
|
||||
if (!"-".equals(name)) {
|
||||
parsingTests(name + " 1", "1 * " + name + " 2");
|
||||
}
|
||||
if (allLetterAndDigit(name)) {
|
||||
parsingTests("5" + name + "5", "5 " + name + "5", "5 " + name + "5 5", "1" + name + "x 1", "1 " + name + "x 1");
|
||||
}
|
||||
super.binary(name, priority, op);
|
||||
}
|
||||
|
||||
private static boolean allLetterAndDigit(final String name) {
|
||||
return name.chars().allMatch(Character::isLetterOrDigit);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void test(final ParserTestSet.ParsedKind<?, ?> kind) {
|
||||
new ExceptionsTestSet<>(this, kind).test();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int cast(final long value) {
|
||||
return Reason.overflow(value);
|
||||
}
|
||||
}
|
||||
13
java/expression/exceptions/ListParser.java
Normal file
13
java/expression/exceptions/ListParser.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package expression.exceptions;
|
||||
|
||||
import expression.ListExpression;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ListParser {
|
||||
ListExpression parse(String expression, final List<String> variables) throws Exception;
|
||||
}
|
||||
8
java/expression/exceptions/package-info.java
Normal file
8
java/expression/exceptions/package-info.java
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Tests for <a href="https://www.kgeorgiy.info/courses/prog-intro/homeworks.html#expressions-exceptions">Expression Error Handling</a> homework
|
||||
* of <a href="https://www.kgeorgiy.info/courses/prog-intro/">Introduction to Programming</a> course.
|
||||
*
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
package expression.exceptions;
|
||||
7
java/expression/package-info.java
Normal file
7
java/expression/package-info.java
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Tests for <a href="https://www.kgeorgiy.info/courses/prog-intro/homeworks.html#expressions">Expressions</a> homework
|
||||
* of <a href="https://www.kgeorgiy.info/courses/prog-intro/">Introduction to Programming</a> course.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
package expression;
|
||||
13
java/expression/parser/ListParser.java
Normal file
13
java/expression/parser/ListParser.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package expression.parser;
|
||||
|
||||
import expression.ListExpression;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ListParser {
|
||||
ListExpression parse(String expression, List<String> variables);
|
||||
}
|
||||
148
java/expression/parser/Operations.java
Normal file
148
java/expression/parser/Operations.java
Normal file
@@ -0,0 +1,148 @@
|
||||
package expression.parser;
|
||||
|
||||
import expression.ToMiniString;
|
||||
import expression.common.ExpressionKind;
|
||||
import expression.common.Reason;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.function.*;
|
||||
import java.util.stream.LongStream;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class Operations {
|
||||
// === Base
|
||||
|
||||
public static final Operation NEGATE = unary("-", 1, a -> -a);
|
||||
@SuppressWarnings("Convert2MethodRef")
|
||||
public static final Operation ADD = binary("+", 1600, (a, b) -> a + b);
|
||||
public static final Operation SUBTRACT = binary("-", 1602, (a, b) -> a - b);
|
||||
public static final Operation MULTIPLY = binary("*", 2001, (a, b) -> a * b);
|
||||
public static final Operation DIVIDE = binary("/", 2002, (a, b) -> b == 0 ? Reason.DBZ.error() : a / b);
|
||||
|
||||
|
||||
// === MinMax
|
||||
public static final Operation MIN = binary("min", 401, Math::min);
|
||||
public static final Operation MAX = binary("max", 401, Math::max);
|
||||
|
||||
|
||||
// === Reverse
|
||||
|
||||
private static Operation digits(final String name, final boolean mask, final int r, final LongBinaryOperator q) {
|
||||
return unary(name, 1, v -> LongStream.iterate(mask ? v & 0xffff_ffffL : v, n -> n != 0, n -> n / r)
|
||||
.map(n -> n % r)
|
||||
.reduce(0, q));
|
||||
}
|
||||
|
||||
public static final Operation REVERSE = digits("reverse", false, 10, (a, b) -> a * 10 + b);
|
||||
|
||||
|
||||
// === Digits
|
||||
public static final Operation DIGITS = digits("digits", false, 10, Long::sum);
|
||||
|
||||
|
||||
// === Floor and Ceiling
|
||||
|
||||
private static long floor(final long a) {
|
||||
return (a >= 0 ? a : a - FLOOR_CEILING_STEP + 1) / FLOOR_CEILING_STEP * FLOOR_CEILING_STEP;
|
||||
}
|
||||
|
||||
private static long ceiling(final long a) {
|
||||
return (a >= 0 ? a + FLOOR_CEILING_STEP - 1: a) / FLOOR_CEILING_STEP * FLOOR_CEILING_STEP;
|
||||
}
|
||||
|
||||
public static final int FLOOR_CEILING_STEP = 1000;
|
||||
public static final Operation FLOOR = unary("floor", 1, Operations::floor);
|
||||
public static final Operation CEILING = unary("ceiling", 1, Operations::ceiling);
|
||||
|
||||
// === Set, Clear
|
||||
|
||||
@SuppressWarnings("IntegerMultiplicationImplicitCastToLong")
|
||||
public static final Operation SET = binary("set", 202, (a, b) -> a | (1 << b));
|
||||
@SuppressWarnings("IntegerMultiplicationImplicitCastToLong")
|
||||
public static final Operation CLEAR = binary("clear", 202, (a, b) -> a & ~(1 << b));
|
||||
|
||||
|
||||
// === Pow, Log
|
||||
public static final Operation POW_O = binary("**", 2402, (a, b) ->
|
||||
b < 0 ? 1 : BigInteger.valueOf(a).modPow(BigInteger.valueOf(b), BigInteger.valueOf(1L << 32)).intValue());
|
||||
public static final Operation LOG_O = binary("//", 2402, (a, b) ->
|
||||
a == 0 && b > 0 ? Integer.MIN_VALUE :
|
||||
a <= 0 || b <= 0 || a == 1 && b == 1 ? 0 :
|
||||
a > 1 && b == 1 ? Integer.MAX_VALUE
|
||||
: LongStream.iterate(b, v -> v <= a, v -> v * b).count()
|
||||
);
|
||||
|
||||
private static final Reason INVALID_POW = new Reason("Invalid power");
|
||||
public static final Operation POW = binary("**", 2402, Operations::powC);
|
||||
|
||||
private static long powC(final long a, final long b) {
|
||||
if (b < 0 || a == 0 && b == 0) {
|
||||
return INVALID_POW.error();
|
||||
}
|
||||
if (Math.abs(a) > 1 && b > 32) {
|
||||
return Reason.OVERFLOW.error();
|
||||
}
|
||||
final BigInteger result = BigInteger.valueOf(a).pow((int) b);
|
||||
if (result.compareTo(BigInteger.valueOf(Integer.MIN_VALUE)) < 0 || BigInteger.valueOf(Integer.MAX_VALUE).compareTo(result) < 0) {
|
||||
return Reason.OVERFLOW.error();
|
||||
}
|
||||
return result.intValue();
|
||||
}
|
||||
|
||||
private static final Reason INVALID_LOG = new Reason("Invalid log");
|
||||
public static final Operation LOG = binary("//", 2402, (a, b) ->
|
||||
a <= 0 || b <= 1 ? INVALID_LOG.error() : (int) (Math.log(a) / Math.log(b)));
|
||||
|
||||
|
||||
// Pow2, Log2
|
||||
|
||||
private static final Reason NEG_LOG = new Reason("Logarithm of negative value");
|
||||
public static final Operation LOG_2
|
||||
= unary("log₂", 1, NEG_LOG.less(1, a-> (long) (Math.log(a) / Math.log(2))));
|
||||
|
||||
private static final Reason NEG_POW = new Reason("Exponentiation to negative power");
|
||||
public static final Operation POW_2
|
||||
= unary("pow₂", 1, NEG_POW.less(0, Reason.OVERFLOW.greater(31, a -> (long) Math.pow(2, a))));
|
||||
|
||||
|
||||
// === High, Low
|
||||
public static final Operation HIGH = unary("high", 1, v -> Integer.highestOneBit((int) v));
|
||||
public static final Operation LOW = unary("low", 1, v -> Integer.lowestOneBit((int) v));
|
||||
|
||||
// === Common
|
||||
|
||||
private Operations() {
|
||||
}
|
||||
|
||||
public static Operation unary(final String name, final int priority, final LongUnaryOperator op) {
|
||||
return unary(name, priority, (a, c) -> op.applyAsLong(a));
|
||||
}
|
||||
|
||||
public static Operation unary(final String left, final String right, final LongUnaryOperator op) {
|
||||
return unary(left, right, (a, c) -> op.applyAsLong(a));
|
||||
}
|
||||
|
||||
public static Operation unary(final String name, final int priority, final BiFunction<Long, LongToIntFunction, Long> op) {
|
||||
return tests -> tests.unary(name, priority, op);
|
||||
}
|
||||
|
||||
public static Operation unary(final String left, final String right, final BiFunction<Long, LongToIntFunction, Long> op) {
|
||||
return tests -> tests.unary(left, right, op);
|
||||
}
|
||||
|
||||
public static Operation binary(final String name, final int priority, final LongBinaryOperator op) {
|
||||
return tests -> tests.binary(name, priority, op);
|
||||
}
|
||||
|
||||
public static <E extends ToMiniString, C> Operation kind(
|
||||
final ExpressionKind<E, C> kind,
|
||||
final ParserTestSet.Parser<E> parser
|
||||
) {
|
||||
return factory -> factory.kind(kind, parser);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Operation extends Consumer<ParserTester> {}
|
||||
}
|
||||
31
java/expression/parser/ParserTest.java
Normal file
31
java/expression/parser/ParserTest.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package expression.parser;
|
||||
|
||||
import base.Selector;
|
||||
import expression.ListExpression;
|
||||
|
||||
import static expression.parser.Operations.*;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class ParserTest {
|
||||
private static final ExpressionParser PARSER = new ExpressionParser();
|
||||
private static final Operations.Operation LIST = kind(ListExpression.KIND, PARSER::parse);
|
||||
|
||||
// === Common
|
||||
|
||||
public static final Selector SELECTOR = Selector.composite(ParserTest.class, ParserTester::new, "easy", "hard")
|
||||
.variant("Base", LIST, ADD, SUBTRACT, MULTIPLY, DIVIDE, NEGATE)
|
||||
.variant("3637", MIN, MAX, REVERSE)
|
||||
.variant("3839", MIN, MAX, REVERSE, DIGITS)
|
||||
.variant("3435", FLOOR, CEILING, SET, CLEAR)
|
||||
.variant("3233", FLOOR, CEILING)
|
||||
.selector();
|
||||
|
||||
private ParserTest() {
|
||||
}
|
||||
|
||||
public static void main(final String... args) {
|
||||
SELECTOR.main(args);
|
||||
}
|
||||
}
|
||||
236
java/expression/parser/ParserTestSet.java
Normal file
236
java/expression/parser/ParserTestSet.java
Normal file
@@ -0,0 +1,236 @@
|
||||
package expression.parser;
|
||||
|
||||
import base.*;
|
||||
import expression.ToMiniString;
|
||||
import expression.common.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class ParserTestSet<E extends ToMiniString, C> {
|
||||
private static final int D = 5;
|
||||
|
||||
private static final List<Integer> TEST_VALUES = new ArrayList<>();
|
||||
static {
|
||||
Functional.addRange(TEST_VALUES, D, D);
|
||||
Functional.addRange(TEST_VALUES, D, -D);
|
||||
}
|
||||
|
||||
public static final List<Integer> CONSTS
|
||||
= List.of(0, 1, -1, 4, -4, 10, -10, 30, -30, 100, -100, Integer.MAX_VALUE, Integer.MIN_VALUE);
|
||||
|
||||
protected final ParserTester tester;
|
||||
protected final ParsedKind<E, C> kind;
|
||||
private final boolean safe;
|
||||
|
||||
protected final TestCounter counter;
|
||||
|
||||
public ParserTestSet(final ParserTester tester, final ParsedKind<E, C> kind) {
|
||||
this(tester, kind, true);
|
||||
}
|
||||
|
||||
protected ParserTestSet(final ParserTester tester, final ParsedKind<E, C> kind, final boolean safe) {
|
||||
this.tester = tester;
|
||||
this.kind = kind;
|
||||
this.safe = safe;
|
||||
|
||||
counter = tester.getCounter();
|
||||
}
|
||||
|
||||
private void examples(final TestGenerator<Integer, E> generator) {
|
||||
example(generator, "$x+2", (x, y, z) -> x + 2);
|
||||
example(generator, "2-$y", (x, y, z) -> 2 - y);
|
||||
example(generator, " 3* $z ", (x, y, z) -> 3 * z);
|
||||
example(generator, "$x/ - 2", (x, y, z) -> -x / 2);
|
||||
example(generator, "$x*$y+($z-1 )/10", (x, y, z) -> x * y + (int) (z - 1) / 10);
|
||||
example(generator, "-(-(-\t\t-5 + 16 *$x*$y) + 1 * $z) -(((-11)))", (x, y, z) -> -(-(5 + 16 * x * y) + z) + 11);
|
||||
example(generator, "" + Integer.MAX_VALUE, (x, y, z) -> (long) Integer.MAX_VALUE);
|
||||
example(generator, "" + Integer.MIN_VALUE, (x, y, z) -> (long) Integer.MIN_VALUE);
|
||||
example(generator, "$x--$y--$z", (x, y, z) -> x + y + z);
|
||||
example(generator, "((2+2))-0/(--2)*555", (x, y, z) -> 4L);
|
||||
example(generator, "$x-$x+$y-$y+$z-($z)", (x, y, z) -> 0L);
|
||||
example(generator, "(".repeat(300) + "$x + $y + (-10*-$z)" + ")".repeat(300), (x, y, z) -> x + y + 10 * z);
|
||||
example(generator, "$x / $y / $z", (x, y, z) -> y == 0 || z == 0 ? Reason.DBZ.error() : (int) x / (int) y / z);
|
||||
}
|
||||
|
||||
private void example(final TestGenerator<Integer, E> generator, final String expr, final ExampleExpression expression) {
|
||||
final List<String> names = Functional.map(generator.variables(3), Pair::first);
|
||||
final TExpression expected = vars -> expression.evaluate(vars.get(0), vars.get(1), vars.get(2));
|
||||
|
||||
counter.test(() -> {
|
||||
final String mangled = mangle(expr, names);
|
||||
final E parsed = parse(mangled, names, true);
|
||||
Functional.allValues(TEST_VALUES, 3).forEach(values -> check(expected, parsed, names, values, mangled));
|
||||
});
|
||||
}
|
||||
|
||||
protected static String mangle(final String expr, final List<String> names) {
|
||||
return expr
|
||||
.replace("$x", names.get(0))
|
||||
.replace("$y", names.get(1))
|
||||
.replace("$z", names.get(2));
|
||||
}
|
||||
|
||||
protected void test() {
|
||||
final TestGenerator<Integer, E> generator = tester.generator.build(kind.kind.variables());
|
||||
final Renderer<Integer, Unit, TExpression> renderer = tester.renderer.build();
|
||||
final Consumer<TestGenerator.Test<Integer, E>> consumer = test -> test(renderer, test);
|
||||
counter.scope("Basic tests", () -> generator.testBasic(consumer));
|
||||
counter.scope("Handmade tests", () -> examples(generator));
|
||||
counter.scope("Random tests", () -> generator.testRandom(counter, 1, consumer));
|
||||
}
|
||||
|
||||
private void test(final Renderer<Integer, Unit, TExpression> renderer, final TestGenerator.Test<Integer, E> test) {
|
||||
final Expr<Integer, E> expr = test.expr;
|
||||
final List<Pair<String, E>> vars = expr.variables();
|
||||
final List<String> variables = Functional.map(vars, Pair::first);
|
||||
final String full = test.render(NodeRenderer.FULL);
|
||||
final String mini = test.render(NodeRenderer.MINI);
|
||||
|
||||
final E fullParsed = parse(test, variables, NodeRenderer.FULL);
|
||||
final E miniParsed = parse(test, variables, NodeRenderer.MINI);
|
||||
final E safeParsed = parse(test, variables, NodeRenderer.SAME);
|
||||
|
||||
checkToString(full, mini, "base", fullParsed);
|
||||
if (tester.mode() > 0) {
|
||||
counter.test(() -> Asserts.assertEquals("mini.toMiniString", mini, miniParsed.toMiniString()));
|
||||
counter.test(() -> Asserts.assertEquals("safe.toMiniString", mini, safeParsed.toMiniString()));
|
||||
}
|
||||
checkToString(full, mini, "extraParentheses", parse(test, variables, NodeRenderer.FULL_EXTRA));
|
||||
checkToString(full, mini, "noSpaces", parse(removeSpaces(full), variables, false));
|
||||
checkToString(full, mini, "extraSpaces", parse(extraSpaces(full), variables, false));
|
||||
|
||||
final TExpression expected = renderer.render(
|
||||
Expr.of(
|
||||
expr.node(),
|
||||
Functional.map(vars, (i, var) -> Pair.of(var.first(), args -> args.get(i)))
|
||||
),
|
||||
Unit.INSTANCE
|
||||
);
|
||||
|
||||
check(expected, fullParsed, variables, tester.random().random(variables.size(), ExtendedRandom::nextInt), full);
|
||||
if (this.safe) {
|
||||
final String safe = test.render(NodeRenderer.SAME);
|
||||
check(expected, safeParsed, variables, tester.random().random(variables.size(), ExtendedRandom::nextInt), safe);
|
||||
}
|
||||
}
|
||||
|
||||
private E parse(
|
||||
final TestGenerator.Test<Integer, E> test,
|
||||
final List<String> variables,
|
||||
final NodeRenderer.Settings settings
|
||||
) {
|
||||
return parse(test.render(settings.withParens(tester.parens)), variables, false);
|
||||
}
|
||||
|
||||
private static final String LOOKBEHIND = "(?<![a-zA-Z0-9<>*/+=!-])";
|
||||
private static final String LOOKAHEAD = "(?![a-zA-Z0-9<>*/])";
|
||||
private static final Pattern SPACES = Pattern.compile(LOOKBEHIND + " | " + LOOKAHEAD + "|" + LOOKAHEAD + LOOKBEHIND);
|
||||
private String extraSpaces(final String expression) {
|
||||
return SPACES.matcher(expression).replaceAll(r -> tester.random().randomString(
|
||||
ExtendedRandom.SPACES,
|
||||
tester.random().nextInt(5)
|
||||
));
|
||||
}
|
||||
|
||||
private static String removeSpaces(final String expression) {
|
||||
return SPACES.matcher(expression).replaceAll("");
|
||||
}
|
||||
|
||||
private void checkToString(final String full, final String mini, final String context, final ToMiniString parsed) {
|
||||
counter.test(() -> {
|
||||
assertEquals(context + ".toString", full, full, parsed.toString());
|
||||
if (tester.mode() > 0) {
|
||||
assertEquals(context + ".toMiniString", full, mini, parsed.toMiniString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void assertEquals(
|
||||
final String context,
|
||||
final String original,
|
||||
final String expected,
|
||||
final String actual
|
||||
) {
|
||||
final String message = String.format("%s:%n original `%s`,%n expected `%s`,%n actual `%s`",
|
||||
context, original, expected, actual);
|
||||
Asserts.assertTrue(message, Objects.equals(expected, actual));
|
||||
}
|
||||
|
||||
private Either<Reason, Integer> eval(final TExpression expression, final List<Integer> vars) {
|
||||
return Reason.eval(() -> tester.cast(expression.evaluate(vars)));
|
||||
}
|
||||
|
||||
protected E parse(final String expression, final List<String> variables, final boolean reparse) {
|
||||
return counter.testV(() -> {
|
||||
final E parsed = counter.testV(() -> counter.call("parse",
|
||||
() -> kind.parse(expression, variables)));
|
||||
if (reparse) {
|
||||
counter.testV(() -> counter.call("parse", () -> kind.parse(parsed.toString(), variables)));
|
||||
}
|
||||
return parsed;
|
||||
});
|
||||
}
|
||||
|
||||
private void check(
|
||||
final TExpression expectedExpression,
|
||||
final E expression,
|
||||
final List<String> variables,
|
||||
final List<Integer> values,
|
||||
final String unparsed
|
||||
) {
|
||||
counter.test(() -> {
|
||||
final Either<Reason, Integer> answer = eval(expectedExpression, values);
|
||||
final String args = IntStream.range(0, variables.size())
|
||||
.mapToObj(i -> variables.get(i) + "=" + values.get(i))
|
||||
.collect(Collectors.joining(", "));
|
||||
final String message = String.format("f(%s)%n\twhere f=%s%n\tyour f=%s", args, unparsed, expression);
|
||||
try {
|
||||
final C actual = kind.kind.evaluate(expression, variables, kind.kind.fromInts(values));
|
||||
counter.checkTrue(answer.isRight(), "Error expected for f(%s)%n\twhere f=%s%n\tyour f=%s", args, unparsed, expression);
|
||||
Asserts.assertEquals(message, answer.getRight(), actual);
|
||||
} catch (final Exception e) {
|
||||
if (answer.isRight()) {
|
||||
counter.fail(e, "No error expected for %s", message);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface TExpression {
|
||||
long evaluate(List<Integer> vars);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
protected interface ExampleExpression {
|
||||
long evaluate(long x, long y, long z);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public record ParsedKind<E extends ToMiniString, C>(ExpressionKind<E, C> kind, Parser<E> parser) {
|
||||
public E parse(final String expression, final List<String> variables) throws Exception {
|
||||
return parser.parse(expression, variables);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return kind.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Parser<E> {
|
||||
E parse(final String expression, final List<String> variables) throws Exception;
|
||||
}
|
||||
}
|
||||
76
java/expression/parser/ParserTester.java
Normal file
76
java/expression/parser/ParserTester.java
Normal file
@@ -0,0 +1,76 @@
|
||||
package expression.parser;
|
||||
|
||||
import base.ExtendedRandom;
|
||||
import base.TestCounter;
|
||||
import base.Tester;
|
||||
import base.Unit;
|
||||
import expression.ToMiniString;
|
||||
import expression.common.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.LongBinaryOperator;
|
||||
import java.util.function.LongToIntFunction;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class ParserTester extends Tester {
|
||||
/* package-private */ final TestGeneratorBuilder<Integer> generator;
|
||||
/* package-private */ final Renderer.Builder<Integer, Unit, ParserTestSet.TExpression> renderer;
|
||||
private final List<ParserTestSet.ParsedKind<?, ?>> kinds = new ArrayList<>();
|
||||
/* package-private */ final List<NodeRenderer.Paren> parens = new ArrayList<>(List.of(NodeRenderer.paren("(", ")")));
|
||||
|
||||
public ParserTester(final TestCounter counter) {
|
||||
super(counter);
|
||||
renderer = Renderer.builder(c -> vars -> c);
|
||||
final ExtendedRandom random = counter.random();
|
||||
generator = new TestGeneratorBuilder<>(random, random::nextInt, ParserTestSet.CONSTS, true);
|
||||
}
|
||||
|
||||
public void unary(final String name, final int priority, final BiFunction<Long, LongToIntFunction, Long> op) {
|
||||
generator.unary(name, priority);
|
||||
renderer.unary(name, (unit, a) -> vars -> cast(op.apply(a.evaluate(vars), this::cast)));
|
||||
}
|
||||
|
||||
public void unary(final String left, final String right, final BiFunction<Long, LongToIntFunction, Long> op) {
|
||||
generator.unary(left, right);
|
||||
renderer.unary(left, (unit, a) -> vars -> cast(op.apply(a.evaluate(vars), this::cast)));
|
||||
}
|
||||
|
||||
public void binary(final String name, final int priority, final LongBinaryOperator op) {
|
||||
generator.binary(name, priority);
|
||||
renderer.binary(name, (unit, a, b) -> vars -> cast(op.applyAsLong(a.evaluate(vars), b.evaluate(vars))));
|
||||
}
|
||||
|
||||
<E extends ToMiniString, C> void kind(final ExpressionKind<E, C> kind, final ParserTestSet.Parser<E> parser) {
|
||||
kinds.add(new ParserTestSet.ParsedKind<>(kind, parser));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void test() {
|
||||
for (final ParserTestSet.ParsedKind<?, ?> kind : kinds) {
|
||||
counter.scope(kind.toString(), () -> test(kind));
|
||||
}
|
||||
}
|
||||
|
||||
protected void test(final ParserTestSet.ParsedKind<?, ?> kind) {
|
||||
new ParserTestSet<>(this, kind).test();
|
||||
}
|
||||
|
||||
public TestCounter getCounter() {
|
||||
return counter;
|
||||
}
|
||||
|
||||
protected int cast(final long value) {
|
||||
return (int) value;
|
||||
}
|
||||
|
||||
public void parens(final String... parens) {
|
||||
assert parens.length % 2 == 0 : "Parens should come in pairs";
|
||||
for (int i = 0; i < parens.length; i += 2) {
|
||||
this.parens.add(NodeRenderer.paren(parens[i], parens[i + 1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
7
java/expression/parser/package-info.java
Normal file
7
java/expression/parser/package-info.java
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Tests for <a href="https://www.kgeorgiy.info/courses/prog-intro/homeworks.html#expressions-parsing">Expressions Parsing</a> homework
|
||||
* of <a href="https://www.kgeorgiy.info/courses/prog-intro/">Introduction to Programming</a> course.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
package expression.parser;
|
||||
248
java/markup/MarkupListTest.java
Normal file
248
java/markup/MarkupListTest.java
Normal file
File diff suppressed because one or more lines are too long
97
java/markup/MarkupTest.java
Normal file
97
java/markup/MarkupTest.java
Normal file
File diff suppressed because one or more lines are too long
71
java/markup/MarkupTester.java
Normal file
71
java/markup/MarkupTester.java
Normal file
@@ -0,0 +1,71 @@
|
||||
package markup;
|
||||
|
||||
import base.Asserts;
|
||||
import base.BaseChecker;
|
||||
import base.TestCounter;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class MarkupTester {
|
||||
private final Map<String, String> mapping;
|
||||
private final String toString;
|
||||
|
||||
private MarkupTester(final Map<String, String> mapping, final String toString) {
|
||||
this.mapping = mapping;
|
||||
this.toString = toString;
|
||||
}
|
||||
|
||||
public static Consumer<TestCounter> variant(final Consumer<Checker> checker, final String name, final Map<String, String> mapping) {
|
||||
return counter -> test(checker).accept(new MarkupTester(mapping, "to" + name), counter);
|
||||
}
|
||||
|
||||
public static BiConsumer<MarkupTester, TestCounter> test(final Consumer<Checker> tester) {
|
||||
return (checker, counter) -> tester.accept(checker.new Checker(counter));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toString;
|
||||
}
|
||||
|
||||
public class Checker extends BaseChecker {
|
||||
public Checker(final TestCounter counter) {
|
||||
super(counter);
|
||||
}
|
||||
|
||||
private <T> MethodHandle findMethod(final T value) {
|
||||
try {
|
||||
return MethodHandles.publicLookup().findVirtual(value.getClass(), toString, MethodType.methodType(void.class, StringBuilder.class));
|
||||
} catch (final NoSuchMethodException | IllegalAccessException e) {
|
||||
throw Asserts.error("Cannot find method 'void %s(StringBuilder)' for %s", toString, value.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
public <T> void test(final T value, String expectedTemplate) {
|
||||
final MethodHandle method = findMethod(value);
|
||||
for (final Map.Entry<String, String> entry : mapping.entrySet()) {
|
||||
expectedTemplate = expectedTemplate.replace(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
final String expected = expectedTemplate;
|
||||
counter.println("Test " + counter.getTestNo());
|
||||
counter.test(() -> {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
try {
|
||||
method.invoke(value, sb);
|
||||
} catch (final Throwable e) {
|
||||
throw Asserts.error("%s(StringBuilder) for %s thrown exception: %s", toString, value.getClass(), e);
|
||||
}
|
||||
Asserts.assertEquals("Result", expected, sb.toString());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
7
java/markup/package-info.java
Normal file
7
java/markup/package-info.java
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Tests for <a href="https://www.kgeorgiy.info/courses/prog-intro/homeworks.html#markup">Markup</a> homework
|
||||
* of <a href="https://www.kgeorgiy.info/courses/prog-intro/">Introduction to Programming</a> course.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
package markup;
|
||||
71
java/md2html/Md2HtmlTest.java
Normal file
71
java/md2html/Md2HtmlTest.java
Normal file
@@ -0,0 +1,71 @@
|
||||
package md2html;
|
||||
|
||||
import base.Selector;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class Md2HtmlTest {
|
||||
// === 3637
|
||||
private static final Consumer<? super Md2HtmlTester> INS = tester -> tester
|
||||
.test("<<вставка>>", "<p><ins>вставка</ins></p>")
|
||||
.test("Это <<вставка>>, вложенная в текст", "<p>Это <ins>вставка</ins>, вложенная в текст</p>")
|
||||
.spoiled("Это не <<вставка>>", "<p>Это не <<вставка>></p>", "<", ">")
|
||||
.spoiled("Это не <<вставка>> 2", "<p>Это не <<вставка>> 2</p>", "<", ">")
|
||||
.addElement("ins", "<<", ">>");
|
||||
private static final Consumer<? super Md2HtmlTester> DEL = tester -> tester
|
||||
.test("}}удаление{{", "<p><del>удаление</del></p>")
|
||||
.test("Это }}удаление{{, вложенное в текст", "<p>Это <del>удаление</del>, вложенное в текст</p>")
|
||||
.spoiled("Это не }}удаление{{", "<p>Это не }}удаление{{</p>", "{")
|
||||
.spoiled("Это не }}удаление{{ 2", "<p>Это не }}удаление{{ 2</p>", "{")
|
||||
.addElement("del", "}}", "{{");
|
||||
|
||||
// === 3839
|
||||
private static final Consumer<? super Md2HtmlTester> PRE = tester -> tester
|
||||
.test("```код __без__ форматирования```", "<p><pre>код __без__ форматирования</pre></p>")
|
||||
.test(
|
||||
"Это не `\\``код __без__ форматирования``\\`",
|
||||
"<p>Это не <code>`</code>код <strong>без</strong> форматирования<code></code>`</p>"
|
||||
)
|
||||
.addElement("pre", "```", (checker, markup, input, output) -> {
|
||||
final String contentS = checker.generateInput(markup).replace("`", "");
|
||||
|
||||
input.append("```").append(contentS).append("```");
|
||||
output.append("<pre>").append(contentS.replace("<", "<").replace(">", "")).append("</pre>");
|
||||
});
|
||||
|
||||
// === 3435
|
||||
private static final Consumer<? super Md2HtmlTester> SAMP = tester -> tester
|
||||
.test("!!пример!!", "<p><samp>пример</samp></p>")
|
||||
.test("Это !!пример!!, вложенный в текст", "<p>Это <samp>пример</samp>, вложенный в текст</p>")
|
||||
.spoiled("Это не !!пример!!", "<p>Это не !!пример!!</p>", "!")
|
||||
.spoiled("Это не !!пример!! 2", "<p>Это не !!пример!! 2</p>", "!")
|
||||
.addElement("samp", "!!");
|
||||
|
||||
// === 3233
|
||||
private static final Consumer<Md2HtmlTester> VAR = tester -> tester
|
||||
.test("%переменная%", "<p><var>переменная</var></p>")
|
||||
.test("Это %переменная%, вложенная в текст", "<p>Это <var>переменная</var>, вложенная в текст</p>")
|
||||
.spoiled("Это не %переменная%", "<p>Это не %переменная%</p>", "%")
|
||||
.spoiled("Это не %переменная% 2", "<p>Это не %переменная% 2</p>", "%")
|
||||
.addElement("var", "%");
|
||||
|
||||
// === Common
|
||||
|
||||
public static final Selector SELECTOR = Selector.composite(Md2HtmlTest.class, c -> new Md2HtmlTester(), Md2HtmlTester::test)
|
||||
.variant("Base")
|
||||
.variant("3637", INS, DEL)
|
||||
.variant("3839", PRE)
|
||||
.variant("3435", SAMP)
|
||||
.variant("3233", VAR)
|
||||
.selector();
|
||||
|
||||
private Md2HtmlTest() {
|
||||
}
|
||||
|
||||
public static void main(final String... args) {
|
||||
SELECTOR.main(args);
|
||||
}
|
||||
}
|
||||
355
java/md2html/Md2HtmlTester.java
Normal file
355
java/md2html/Md2HtmlTester.java
Normal file
@@ -0,0 +1,355 @@
|
||||
package md2html;
|
||||
|
||||
import base.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class Md2HtmlTester {
|
||||
private static final Map<String, String> ESCAPES = Map.of("<", "<", ">", ">");
|
||||
|
||||
private final Map<String, Generator> elements = new HashMap<>();
|
||||
private final Map<String, List<String>> tags = new LinkedHashMap<>();
|
||||
private final List<Pair<String, String>> tests = new ArrayList<>();
|
||||
|
||||
public Md2HtmlTester() {
|
||||
addElement("em", "*");
|
||||
addElement("em", "_");
|
||||
addElement("strong", "**");
|
||||
addElement("strong", "__");
|
||||
addElement("s", "--");
|
||||
addElement("code", "`");
|
||||
|
||||
test(
|
||||
"# Заголовок первого уровня\n\n",
|
||||
"<h1>Заголовок первого уровня</h1>"
|
||||
);
|
||||
test(
|
||||
"## Второго\n\n",
|
||||
"<h2>Второго</h2>"
|
||||
);
|
||||
test(
|
||||
"### Третьего ## уровня\n\n",
|
||||
"<h3>Третьего ## уровня</h3>"
|
||||
);
|
||||
test(
|
||||
"#### Четвертого\n# Все еще четвертого\n\n",
|
||||
"<h4>Четвертого\n# Все еще четвертого</h4>"
|
||||
);
|
||||
test(
|
||||
"Этот абзац текста\nсодержит две строки.",
|
||||
"<p>Этот абзац текста\nсодержит две строки.</p>"
|
||||
);
|
||||
test(
|
||||
" # Может показаться, что это заголовок.\nНо нет, это абзац, начинающийся с `#`.\n\n",
|
||||
"<p> # Может показаться, что это заголовок.\nНо нет, это абзац, начинающийся с <code>#</code>.</p>"
|
||||
);
|
||||
test(
|
||||
"#И это не заголовок.\n\n",
|
||||
"<p>#И это не заголовок.</p>"
|
||||
);
|
||||
test(
|
||||
"###### Заголовки могут быть многострочными\n(и с пропуском заголовков предыдущих уровней)\n\n",
|
||||
"<h6>Заголовки могут быть многострочными\n(и с пропуском заголовков предыдущих уровней)</h6>"
|
||||
);
|
||||
test(
|
||||
"Мы все любим *выделять* текст _разными_ способами.\n**Сильное выделение**, используется гораздо реже,\nно __почему бы и нет__?\nНемного --зачеркивания-- еще никому не вредило.\nКод представляется элементом `code`.\n\n",
|
||||
"<p>Мы все любим <em>выделять</em> текст <em>разными</em> способами.\n<strong>Сильное выделение</strong>, используется гораздо реже,\nно <strong>почему бы и нет</strong>?\nНемного <s>зачеркивания</s> еще никому не вредило.\nКод представляется элементом <code>code</code>.</p>"
|
||||
);
|
||||
test(
|
||||
"Обратите внимание, как экранируются специальные\nHTML-символы, такие как `<`, `>` и `&`.\n\n",
|
||||
"<p>Обратите внимание, как экранируются специальные\nHTML-символы, такие как <code><</code>, <code>></code> и <code>&</code>.</p>"
|
||||
);
|
||||
test(
|
||||
"Экранирование должно работать во всех местах: <>&.\n\n",
|
||||
"<p>Экранирование должно работать во всех местах: <>&.</p>"
|
||||
);
|
||||
test(
|
||||
"Знаете ли вы, что в Markdown, одиночные * и _\nне означают выделение?\nОни так же могут быть заэкранированы\nпри помощи обратного слэша: \\*.",
|
||||
"<p>Знаете ли вы, что в Markdown, одиночные * и _\nне означают выделение?\nОни так же могут быть заэкранированы\nпри помощи обратного слэша: *.</p>"
|
||||
);
|
||||
test(
|
||||
"\n\n\nЛишние пустые строки должны игнорироваться.\n\n\n\n",
|
||||
"<p>Лишние пустые строки должны игнорироваться.</p>"
|
||||
);
|
||||
test(
|
||||
"Любите ли вы *вложенные __выделения__* так,\nкак __--люблю--__ их я?",
|
||||
"<p>Любите ли вы <em>вложенные <strong>выделения</strong></em> так,\nкак <strong><s>люблю</s></strong> их я?</p>"
|
||||
);
|
||||
|
||||
test(
|
||||
"""
|
||||
# Заголовок первого уровня
|
||||
|
||||
## Второго
|
||||
|
||||
### Третьего ## уровня
|
||||
|
||||
#### Четвертого
|
||||
# Все еще четвертого
|
||||
|
||||
Этот абзац текста
|
||||
содержит две строки.
|
||||
|
||||
# Может показаться, что это заголовок.
|
||||
Но нет, это абзац, начинающийся с `#`.
|
||||
|
||||
#И это не заголовок.
|
||||
|
||||
###### Заголовки могут быть многострочными
|
||||
(и с пропуском заголовков предыдущих уровней)
|
||||
|
||||
Мы все любим *выделять* текст _разными_ способами.
|
||||
**Сильное выделение**, используется гораздо реже,
|
||||
но __почему бы и нет__?
|
||||
Немного --зачеркивания-- еще никому не вредило.
|
||||
Код представляется элементом `code`.
|
||||
|
||||
Обратите внимание, как экранируются специальные
|
||||
HTML-символы, такие как `<`, `>` и `&`.
|
||||
|
||||
Знаете ли вы, что в Markdown, одиночные * и _
|
||||
не означают выделение?
|
||||
Они так же могут быть заэкранированы
|
||||
при помощи обратного слэша: \\*.
|
||||
|
||||
|
||||
|
||||
Лишние пустые строки должны игнорироваться.
|
||||
|
||||
Любите ли вы *вложенные __выделения__* так,
|
||||
как __--люблю--__ их я?
|
||||
""",
|
||||
"""
|
||||
<h1>Заголовок первого уровня</h1>
|
||||
<h2>Второго</h2>
|
||||
<h3>Третьего ## уровня</h3>
|
||||
<h4>Четвертого
|
||||
# Все еще четвертого</h4>
|
||||
<p>Этот абзац текста
|
||||
содержит две строки.</p>
|
||||
<p> # Может показаться, что это заголовок.
|
||||
Но нет, это абзац, начинающийся с <code>#</code>.</p>
|
||||
<p>#И это не заголовок.</p>
|
||||
<h6>Заголовки могут быть многострочными
|
||||
(и с пропуском заголовков предыдущих уровней)</h6>
|
||||
<p>Мы все любим <em>выделять</em> текст <em>разными</em> способами.
|
||||
<strong>Сильное выделение</strong>, используется гораздо реже,
|
||||
но <strong>почему бы и нет</strong>?
|
||||
Немного <s>зачеркивания</s> еще никому не вредило.
|
||||
Код представляется элементом <code>code</code>.</p>
|
||||
<p>Обратите внимание, как экранируются специальные
|
||||
HTML-символы, такие как <code><</code>, <code>></code> и <code>&</code>.</p>
|
||||
<p>Знаете ли вы, что в Markdown, одиночные * и _
|
||||
не означают выделение?
|
||||
Они так же могут быть заэкранированы
|
||||
при помощи обратного слэша: *.</p>
|
||||
<p>Лишние пустые строки должны игнорироваться.</p>
|
||||
<p>Любите ли вы <em>вложенные <strong>выделения</strong></em> так,
|
||||
как <strong><s>люблю</s></strong> их я?</p>
|
||||
"""
|
||||
);
|
||||
|
||||
test("# Без перевода строки в конце", "<h1>Без перевода строки в конце</h1>");
|
||||
test("# Один перевод строки в конце\n", "<h1>Один перевод строки в конце</h1>");
|
||||
test("# Два перевода строки в конце\n\n", "<h1>Два перевода строки в конце</h1>");
|
||||
test(
|
||||
"Выделение может *начинаться на одной строке,\n а заканчиваться* на другой",
|
||||
"<p>Выделение может <em>начинаться на одной строке,\n а заканчиваться</em> на другой</p>"
|
||||
);
|
||||
test("# *Выделение* и `код` в заголовках", "<h1><em>Выделение</em> и <code>код</code> в заголовках</h1>");
|
||||
}
|
||||
|
||||
protected void addElement(final String tag, final String markup) {
|
||||
addElement(tag, markup, markup);
|
||||
}
|
||||
|
||||
protected void addElement(final String tag, final String begin, final String end) {
|
||||
addElement(tag, begin, (checker, markup, input, output) -> {
|
||||
checker.space(input, output);
|
||||
input.append(begin);
|
||||
open(output, tag);
|
||||
|
||||
checker.word(input, output);
|
||||
checker.generate(markup, input, output);
|
||||
checker.word(input, output);
|
||||
|
||||
input.append(end);
|
||||
close(output, tag);
|
||||
checker.space(input, output);
|
||||
});
|
||||
}
|
||||
|
||||
public void addElement(final String tag, final String begin, final Generator generator) {
|
||||
Asserts.assertTrue("Duplicate element " + begin, elements.put(begin, generator) == null);
|
||||
tags.computeIfAbsent(tag, k -> new ArrayList<>()).add(begin);
|
||||
}
|
||||
|
||||
private final Runner runner = Runner.packages("md2html").files("Md2Html");
|
||||
|
||||
protected Md2HtmlTester test(final String input, final String output) {
|
||||
tests.add(Pair.of(input, output));
|
||||
return this;
|
||||
}
|
||||
|
||||
protected Md2HtmlTester spoiled(final String input, final String output, final String... spoilers) {
|
||||
for (final String spoiler : spoilers) {
|
||||
final Indexer in = new Indexer(input, spoiler);
|
||||
final Indexer out = new Indexer(output, ESCAPES.getOrDefault(spoiler, spoiler));
|
||||
while (in.next() && out.next()) {
|
||||
tests.add(Pair.of(in.cut(), out.cut()));
|
||||
tests.add(Pair.of(in.escape(), output));
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private static class Indexer {
|
||||
private final String string;
|
||||
private final String spoiler;
|
||||
private int index = - 1;
|
||||
|
||||
public Indexer(final String string, final String spoiler) {
|
||||
this.string = string;
|
||||
this.spoiler = spoiler;
|
||||
}
|
||||
|
||||
public boolean next() {
|
||||
index = string.indexOf(spoiler, index + 1);
|
||||
return index >= 0;
|
||||
}
|
||||
|
||||
public String cut() {
|
||||
return string.substring(0, index) + string.substring(index + spoiler.length());
|
||||
}
|
||||
|
||||
public String escape() {
|
||||
return string.substring(0, index) + "\\" + string.substring(index);
|
||||
}
|
||||
}
|
||||
|
||||
private static void open(final StringBuilder output, final String tag) {
|
||||
output.append("<").append(tag).append(">");
|
||||
}
|
||||
|
||||
private static void close(final StringBuilder output, final String tag) {
|
||||
output.append("</").append(tag).append(">");
|
||||
}
|
||||
|
||||
public void test(final TestCounter counter) {
|
||||
counter.scope("Testing " + String.join(", ", tags.keySet()), () -> new Checker(counter).test());
|
||||
}
|
||||
|
||||
public class Checker extends BaseChecker {
|
||||
public Checker(final TestCounter counter) {
|
||||
super(counter);
|
||||
}
|
||||
|
||||
protected void test() {
|
||||
for (final Pair<String, String> test : tests) {
|
||||
test(test);
|
||||
}
|
||||
|
||||
for (final String markup : elements.keySet()) {
|
||||
randomTest(3, 10, List.of(markup));
|
||||
}
|
||||
|
||||
final int d = TestCounter.DENOMINATOR;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
randomTest(100, 1000, randomMarkup());
|
||||
}
|
||||
randomTest(100, 100_000 / d, randomMarkup());
|
||||
}
|
||||
|
||||
private void test(final Pair<String, String> test) {
|
||||
runner.testEquals(counter, Arrays.asList(test.first().split("\n")), Arrays.asList(test.second().split("\n")));
|
||||
}
|
||||
|
||||
private List<String> randomMarkup() {
|
||||
return Functional.map(tags.values(), random()::randomItem);
|
||||
}
|
||||
|
||||
private void randomTest(final int paragraphs, final int length, final List<String> markup) {
|
||||
final StringBuilder input = new StringBuilder();
|
||||
final StringBuilder output = new StringBuilder();
|
||||
emptyLines(input);
|
||||
final List<String> markupList = new ArrayList<>(markup);
|
||||
for (int i = 0; i < paragraphs; i++) {
|
||||
final StringBuilder inputSB = new StringBuilder();
|
||||
paragraph(length, inputSB, output, markupList);
|
||||
input.append(inputSB);
|
||||
emptyLines(input);
|
||||
}
|
||||
test(Pair.of(input.toString(), output.toString()));
|
||||
}
|
||||
|
||||
private void paragraph(final int length, final StringBuilder input, final StringBuilder output, final List<String> markup) {
|
||||
final int h = random().nextInt(0, 6);
|
||||
final String tag = h == 0 ? "p" : "h" + h;
|
||||
if (h > 0) {
|
||||
input.append(new String(new char[h]).replace('\0', '#')).append(" ");
|
||||
}
|
||||
|
||||
open(output, tag);
|
||||
while (input.length() < length) {
|
||||
generate(markup, input, output);
|
||||
final String middle = random().randomString(ExtendedRandom.ENGLISH);
|
||||
input.append(middle).append("\n");
|
||||
output.append(middle).append("\n");
|
||||
}
|
||||
output.setLength(output.length() - 1);
|
||||
close(output, tag);
|
||||
|
||||
output.append("\n");
|
||||
input.append("\n");
|
||||
}
|
||||
|
||||
private void space(final StringBuilder input, final StringBuilder output) {
|
||||
if (random().nextBoolean()) {
|
||||
final String space = random().nextBoolean() ? " " : "\n";
|
||||
input.append(space);
|
||||
output.append(space);
|
||||
}
|
||||
}
|
||||
|
||||
public void generate(final List<String> markup, final StringBuilder input, final StringBuilder output) {
|
||||
word(input, output);
|
||||
if (markup.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
final String type = random().randomItem(markup);
|
||||
|
||||
markup.remove(type);
|
||||
elements.get(type).generate(this, markup, input, output);
|
||||
markup.add(type);
|
||||
}
|
||||
|
||||
protected void word(final StringBuilder input, final StringBuilder output) {
|
||||
final String word = random().randomString(random().randomItem(ExtendedRandom.ENGLISH, ExtendedRandom.GREEK, ExtendedRandom.RUSSIAN));
|
||||
input.append(word);
|
||||
output.append(word);
|
||||
}
|
||||
|
||||
private void emptyLines(final StringBuilder sb) {
|
||||
while (random().nextBoolean()) {
|
||||
sb.append('\n');
|
||||
}
|
||||
}
|
||||
|
||||
String generateInput(final List<String> markup) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
generate(markup, sb, new StringBuilder());
|
||||
return sb.toString()
|
||||
.replace("<", "")
|
||||
.replace(">", "")
|
||||
.replace("]", "");
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Generator {
|
||||
void generate(Checker checker, List<String> markup, StringBuilder input, StringBuilder output);
|
||||
}
|
||||
}
|
||||
8
java/md2html/package-info.java
Normal file
8
java/md2html/package-info.java
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Tests for <a href="https://www.kgeorgiy.info/courses/prog-intro/homeworks.html#md2html">Markdown to HTML</a> homework
|
||||
* of <a href="https://www.kgeorgiy.info/courses/prog-intro/">Introduction to Programming</a> course.
|
||||
*
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
package md2html;
|
||||
87
java/reverse/FastReverseTest.java
Normal file
87
java/reverse/FastReverseTest.java
Normal file
@@ -0,0 +1,87 @@
|
||||
package reverse;
|
||||
|
||||
import base.ExtendedRandom;
|
||||
import base.Named;
|
||||
import base.Selector;
|
||||
import base.TestCounter;
|
||||
import wordStat.WordStatTest;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.LongBinaryOperator;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class FastReverseTest {
|
||||
// === 3637
|
||||
|
||||
private static final Named<BiFunction<ExtendedRandom, Integer, String>> OCT = Named.of("",
|
||||
(r, i) -> r.nextBoolean() ? Integer.toString(i) : Integer.toOctalString(i) + (r.nextBoolean() ? "o" : "O")
|
||||
);
|
||||
private static final Named<BiFunction<ExtendedRandom, Integer, String>> DEC = Named.of("", (r, i) -> Integer.toString(i));
|
||||
|
||||
private static final Named<String> PUNCT = Named.of(
|
||||
"",
|
||||
IntStream.range(0, Character.MAX_VALUE)
|
||||
.filter(ch -> ch == ' ' || Character.getType(ch) == Character.START_PUNCTUATION || Character.getType(ch) == Character.END_PUNCTUATION)
|
||||
.filter(ch -> ch != 13 && ch != 10)
|
||||
.mapToObj(Character::toString)
|
||||
.collect(Collectors.joining())
|
||||
);
|
||||
|
||||
public static final Named<ReverseTester.Op> MIN_C = Named.of("MinC", scan2((a, b) -> b));
|
||||
public static final Named<ReverseTester.Op> MIN = Named.of("Min", scan2(Math::min));
|
||||
|
||||
private static ReverseTester.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.MAX_VALUE);
|
||||
Arrays.fill(cc, Integer.MAX_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.min(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);
|
||||
}
|
||||
|
||||
|
||||
// === Common
|
||||
|
||||
public static final int MAX_SIZE = 1_000_000 / TestCounter.DENOMINATOR / TestCounter.DENOMINATOR;
|
||||
|
||||
public static final Selector SELECTOR = new Selector(FastReverseTest.class)
|
||||
.variant("Base", ReverseTester.variant(MAX_SIZE, ReverseTest.REVERSE))
|
||||
.variant("3637", ReverseTester.variant(MAX_SIZE, "", MIN_C, OCT, DEC, PUNCT))
|
||||
.variant("3839", ReverseTester.variant(MAX_SIZE, "", MIN, OCT, DEC, PUNCT))
|
||||
.variant("3435", ReverseTester.variant(MAX_SIZE, ReverseTest.ROTATE, PUNCT))
|
||||
.variant("3233", ReverseTester.variant(MAX_SIZE, ReverseTest.EVEN, PUNCT))
|
||||
.variant("4142", ReverseTester.variant(MAX_SIZE, ReverseTest.AVG, PUNCT))
|
||||
.variant("4749", ReverseTester.variant(MAX_SIZE, ReverseTest.SUM, PUNCT))
|
||||
|
||||
;
|
||||
|
||||
|
||||
private FastReverseTest() {
|
||||
}
|
||||
|
||||
public static void main(final String... args) {
|
||||
SELECTOR.main(args);
|
||||
WordStatTest.main(args);
|
||||
}
|
||||
}
|
||||
165
java/reverse/ReverseTest.java
Normal file
165
java/reverse/ReverseTest.java
Normal file
@@ -0,0 +1,165 @@
|
||||
package reverse;
|
||||
|
||||
import base.Named;
|
||||
import base.Selector;
|
||||
import base.TestCounter;
|
||||
import reverse.ReverseTester.Op;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Tests for {@code Reverse} homework.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class ReverseTest {
|
||||
// === Base
|
||||
public static final Named<Op> REVERSE = Named.of("", ReverseTester::transform);
|
||||
|
||||
|
||||
// === Max
|
||||
|
||||
public static final Named<Op> MAX_C = Named.of("MaxC", scan2((a, b) -> b));
|
||||
public static final Named<Op> 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<Op> ROTATE = Named.of("Rotate", ints -> {
|
||||
final List<int[]> 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<Op> 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<Op> 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<Op> 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<Op> 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<Op> 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);
|
||||
}
|
||||
}
|
||||
299
java/reverse/ReverseTester.java
Normal file
299
java/reverse/ReverseTester.java
Normal file
@@ -0,0 +1,299 @@
|
||||
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<Op> TRANSFORM = Named.of("", ReverseTester::transform);
|
||||
public static final Named<String> SPACE = Named.of("", " ");
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Op extends Function<int[][], long[][]> {}
|
||||
|
||||
private static final int[] DIVISORS = {100, 10, 1};
|
||||
|
||||
private final Op transform;
|
||||
private final BiFunction<ExtendedRandom, Integer, String> inputToString;
|
||||
private final BiFunction<ExtendedRandom, Integer, String> 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<ExtendedRandom, Integer, String> inputToString,
|
||||
final BiFunction<ExtendedRandom, Integer, String> outputToString
|
||||
) {
|
||||
name = className;
|
||||
this.transform = transform;
|
||||
this.spaces = spaces;
|
||||
this.inputToString = inputToString;
|
||||
this.outputToString = outputToString;
|
||||
}
|
||||
|
||||
private static Consumer<TestCounter> variant(final int maxSize, final Supplier<ReverseTester> tester) {
|
||||
return counter -> tester.get().run(counter, maxSize);
|
||||
}
|
||||
|
||||
public static Consumer<TestCounter> variant(final int maxSize, final Named<Op> transform) {
|
||||
return variant(maxSize, transform, SPACE);
|
||||
}
|
||||
|
||||
|
||||
public static Consumer<TestCounter> variant(final int maxSize, final Named<Op> transform, final Named<String> spaces) {
|
||||
Objects.requireNonNull(transform);
|
||||
Objects.requireNonNull(spaces);
|
||||
return variant(
|
||||
maxSize,
|
||||
() -> new ReverseTester("Reverse" + transform.name() + spaces.name(), transform.value(), spaces.value())
|
||||
);
|
||||
}
|
||||
|
||||
public static Consumer<TestCounter> variant(
|
||||
final int maxSize,
|
||||
final String suffix,
|
||||
final Named<BiFunction<ExtendedRandom, Integer, String>> input,
|
||||
final Named<BiFunction<ExtendedRandom, Integer, String>> output
|
||||
) {
|
||||
return variant(maxSize, suffix, TRANSFORM, input, output);
|
||||
}
|
||||
|
||||
public static Consumer<TestCounter> variant(
|
||||
final int maxSize,
|
||||
final String suffix,
|
||||
final Named<Op> op,
|
||||
final Named<BiFunction<ExtendedRandom, Integer, String>> input,
|
||||
final Named<BiFunction<ExtendedRandom, Integer, String>> output
|
||||
) {
|
||||
return variant(maxSize, suffix, op, input, output, SPACE);
|
||||
}
|
||||
|
||||
public static Consumer<TestCounter> variant(
|
||||
final int maxSize,
|
||||
final String suffix,
|
||||
final Named<Op> op,
|
||||
final Named<BiFunction<ExtendedRandom, Integer, String>> input,
|
||||
final Named<BiFunction<ExtendedRandom, Integer, String>> output,
|
||||
final Named<String> 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<String> 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<int[]> 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<String> inputLines = toLines(input, random().randomString(spaces, 1, 10));
|
||||
final List<String> outputLines = toLines(output, " ");
|
||||
runner.testEquals(counter, inputLines, outputLines);
|
||||
}
|
||||
|
||||
private String[][] toString(final int[][] ints, final BiFunction<ExtendedRandom, Integer, String> 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<ExtendedRandom, Integer, String> 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<String> 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 <T> List<List<T>> permutations(final List<T> elements) {
|
||||
final List<List<T>> result = new ArrayList<>();
|
||||
permutations(new ArrayList<>(elements), result, elements.size() - 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static <T> void permutations(final List<T> elements, final List<List<T>> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
7
java/reverse/package-info.java
Normal file
7
java/reverse/package-info.java
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Tests for <a href="https://www.kgeorgiy.info/courses/prog-intro/homeworks.html#reverse">Reverse</a> homework
|
||||
* of <a href="https://www.kgeorgiy.info/courses/prog-intro/">Introduction to Programming</a> course.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
package reverse;
|
||||
178
java/sum/SumTest.java
Normal file
178
java/sum/SumTest.java
Normal file
@@ -0,0 +1,178 @@
|
||||
package sum;
|
||||
|
||||
import base.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Locale;
|
||||
import java.util.function.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class SumTest {
|
||||
// === Base
|
||||
|
||||
@FunctionalInterface
|
||||
/* package-private */ interface Op<T extends Number> extends UnaryOperator<SumTester<T>> {}
|
||||
|
||||
private static final BiConsumer<Number, String> TO_STRING = (expected, out) -> Asserts.assertEquals("Sum", expected.toString(), out);
|
||||
|
||||
private static final Named<Supplier<SumTester<Integer>>> BASE = Named.of("", () -> new SumTester<>(
|
||||
Integer::sum, n -> (int) n, (r, max) -> r.nextInt() % max, TO_STRING,
|
||||
10, 100, Integer.MAX_VALUE
|
||||
));
|
||||
|
||||
/* package-private */ static <T extends Number> Named<Op<T>> plain() {
|
||||
return Named.of("", test -> test);
|
||||
}
|
||||
|
||||
|
||||
// === DoubleHex
|
||||
|
||||
private static BiConsumer<Number, String> approximate(final Function<String, Number> parser, final double precision) {
|
||||
return (expected, out) ->
|
||||
Asserts.assertEquals("Sum", expected.doubleValue(), parser.apply(out).doubleValue(), precision);
|
||||
}
|
||||
|
||||
private static final Named<Supplier<SumTester<Double>>> DOUBLE = Named.of("Double", () -> new SumTester<>(
|
||||
Double::sum, n -> (double) n, (r, max) -> (r.getRandom().nextDouble() - 0.5) * 2 * max,
|
||||
approximate(Double::parseDouble, 1e-10),
|
||||
10.0, 0.01, 1e20, 1e100, Double.MAX_VALUE / 10000)
|
||||
.test(5, "2.5 2.5")
|
||||
.test(0, "1e100 -1e100")
|
||||
.testT(2e100, "1.5e100 0.5e100"));
|
||||
|
||||
private static <T extends Number> Named<Op<T>> hexFull(final Function<T, String> toHex) {
|
||||
final Function<T, String> toHexSpoiled = toHex.andThen(s ->s.chars()
|
||||
.map(ch -> ((ch ^ s.hashCode()) & 1) == 0 ? Character.toLowerCase(ch) : Character.toUpperCase(ch))
|
||||
.mapToObj(Character::toString)
|
||||
.collect(Collectors.joining()));
|
||||
return Named.of("Hex", test -> test
|
||||
.test(toHex, 1)
|
||||
.test(toHex, 0x1a)
|
||||
.test(toHexSpoiled, 0xA2)
|
||||
.test(toHexSpoiled, 0X0, 0X1, 0XF, 0XF, 0x0, 0x1, 0xF, 0xf)
|
||||
.test(toHexSpoiled, 0x12345678)
|
||||
.test(toHexSpoiled, 0x09abcdef)
|
||||
.test(toHexSpoiled, 0x3CafeBab)
|
||||
.test(toHexSpoiled, 0x3DeadBee)
|
||||
|
||||
.test(toHex, Integer.MAX_VALUE)
|
||||
.test(toHex, Integer.MIN_VALUE)
|
||||
.setToString(number -> {
|
||||
final int hashCode = number.hashCode();
|
||||
if ((hashCode & 1) == 0) {
|
||||
return number.toString();
|
||||
}
|
||||
|
||||
return toHexSpoiled.apply(number);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// === Octal
|
||||
private static <T extends Number> Named<Op<T>> octal(final Function<T, String> toOctal) {
|
||||
//noinspection OctalInteger,StringConcatenationMissingWhitespace
|
||||
return Named.of("Octal", test -> test
|
||||
.test(1, "1o")
|
||||
.test(017, "17o")
|
||||
.testSpaces(6, " 1o 2o 3O ")
|
||||
.test(01234567, "1234567O")
|
||||
|
||||
.test(Integer.MIN_VALUE, "-0" + String.valueOf(Integer.MIN_VALUE).substring(1))
|
||||
.test(Integer.MAX_VALUE, "0" + Integer.MAX_VALUE)
|
||||
.test(Integer.MAX_VALUE, Integer.toOctalString(Integer.MAX_VALUE) + "o")
|
||||
.test(Integer.MAX_VALUE, "0" + Integer.toOctalString(Integer.MAX_VALUE) + "O")
|
||||
.setToString(number -> {
|
||||
final int hashCode = number.hashCode();
|
||||
if ((hashCode & 1) == 0) {
|
||||
return number.toString();
|
||||
}
|
||||
|
||||
final String lower = toOctal.apply(number).toLowerCase(Locale.ROOT) + "o";
|
||||
return (hashCode & 2) == 0 ? lower : lower.toUpperCase(Locale.ROOT);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// === Long
|
||||
|
||||
private static final Named<Supplier<SumTester<Long>>> LONG = Named.of("Long", () -> new SumTester<>(
|
||||
Long::sum, n -> n, (r, max) -> r.getRandom().nextLong() % max, TO_STRING,
|
||||
10L, 100L, (long) Integer.MAX_VALUE, Long.MAX_VALUE)
|
||||
.test(12345678901234567L, " +12345678901234567 ")
|
||||
.test(0L, " +12345678901234567 -12345678901234567")
|
||||
.test(0L, " +12345678901234567 -12345678901234567"));
|
||||
|
||||
// === BigInteger
|
||||
|
||||
private static final Named<Supplier<SumTester<BigInteger>>> BIG_INTEGER = Named.of("BigInteger", () -> new SumTester<>(
|
||||
BigInteger::add, BigInteger::valueOf, (r, max) -> new BigInteger(max.bitLength(), r.getRandom()), TO_STRING,
|
||||
BigInteger.TEN, BigInteger.TEN.pow(10), BigInteger.TEN.pow(100), BigInteger.TWO.pow(1000))
|
||||
.test(0, "10000000000000000000000000000000000000000 -10000000000000000000000000000000000000000"));
|
||||
|
||||
|
||||
// === BigDecimalHex
|
||||
|
||||
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
|
||||
private static final Named<Supplier<SumTester<BigDecimal>>> BIG_DECIMAL = Named.of("BigDecimal", () -> new SumTester<>(
|
||||
BigDecimal::add, BigDecimal::valueOf,
|
||||
(r, max) -> {
|
||||
final BigInteger unscaled = new BigInteger((max.precision() - max.scale() + 2) * 3, r.getRandom());
|
||||
return new BigDecimal(unscaled, 3);
|
||||
},
|
||||
TO_STRING,
|
||||
BigDecimal.TEN, BigDecimal.TEN.pow(10), BigDecimal.TEN.pow(100), BigDecimal.ONE.add(BigDecimal.ONE).pow(1000))
|
||||
.testT(BigDecimal.ZERO.setScale(3), "10000000000000000000000000000000000000000.123 -10000000000000000000000000000000000000000.123"));
|
||||
|
||||
private static String bigDecimalToString(final BigDecimal number) {
|
||||
final int scale = number.scale();
|
||||
return "0x" + number.unscaledValue().toString(16) + (scale == 0 ? "" : "s" + Integer.toString(scale, 16));
|
||||
}
|
||||
|
||||
|
||||
// === Hex
|
||||
|
||||
private static <T extends Number> Named<Op<T>> hex(final Function<T, String> toHex) {
|
||||
return hexFull(v -> "0x" + toHex.apply(v));
|
||||
}
|
||||
|
||||
|
||||
// === Common
|
||||
|
||||
/* package-private */ static <T extends Number> Consumer<TestCounter> variant(
|
||||
final Named<Function<String, Runner>> runner,
|
||||
final Named<Supplier<SumTester<T>>> test,
|
||||
final Named<? extends Function<? super SumTester<T>, ? extends SumTester<?>>> modifier
|
||||
) {
|
||||
return counter -> modifier.value().apply(test.value().get())
|
||||
.test("Sum" + test.name() + modifier.name() + runner.name(), counter, runner.value());
|
||||
}
|
||||
|
||||
/* package-private */ static final Named<Function<String, Runner>> RUNNER =
|
||||
Named.of("", Runner.packages("", "sum")::args);
|
||||
|
||||
public static final Selector SELECTOR = selector(SumTest.class, RUNNER);
|
||||
|
||||
private SumTest() {
|
||||
// Utility class
|
||||
}
|
||||
|
||||
public static Selector selector(final Class<?> owner, final Named<Function<String, Runner>> runner) {
|
||||
return new Selector(owner)
|
||||
.variant("Base", variant(runner, BASE, plain()))
|
||||
.variant("3637", variant(runner, DOUBLE, hexFull(value -> value == value.intValue() && value > 0 ? "0x" + Integer.toHexString(value.intValue()) : Double.toHexString(value))))
|
||||
.variant("3839", variant(runner, BIG_DECIMAL, hexFull(SumTest::bigDecimalToString)))
|
||||
.variant("3435", variant(runner, BASE, hex(Integer::toHexString)))
|
||||
.variant("3233", variant(runner, DOUBLE, plain()))
|
||||
.variant("4749", variant(runner, LONG, octal(Long::toOctalString)))
|
||||
.variant("4142", variant(runner, BIG_INTEGER, octal(number -> number.toString(8))))
|
||||
;
|
||||
}
|
||||
|
||||
public static void main(final String... args) {
|
||||
SELECTOR.main(args);
|
||||
}
|
||||
}
|
||||
189
java/sum/SumTester.java
Normal file
189
java/sum/SumTester.java
Normal file
@@ -0,0 +1,189 @@
|
||||
package sum;
|
||||
|
||||
import base.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.LongStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class SumTester<T extends Number> {
|
||||
private static final List<String> SPACES = List.of(
|
||||
" \t\n\u000B\u2029\f",
|
||||
IntStream.rangeClosed(0, Character.MAX_VALUE)
|
||||
.filter(Character::isWhitespace)
|
||||
.mapToObj(Character::toString)
|
||||
.collect(Collectors.joining())
|
||||
);
|
||||
|
||||
private final BinaryOperator<T> add;
|
||||
private final LongFunction<T> fromLong;
|
||||
private BiFunction<ExtendedRandom, T, String> toString;
|
||||
private final BiFunction<ExtendedRandom, T, T> randomValue;
|
||||
private final BiConsumer<Number, String> verifier;
|
||||
private List<String> spaces;
|
||||
private final List<T> limits;
|
||||
|
||||
private final List<Consumer<Checker>> tests = new ArrayList<>();
|
||||
|
||||
@SafeVarargs
|
||||
public SumTester(
|
||||
final BinaryOperator<T> add,
|
||||
final LongFunction<T> fromLong,
|
||||
final BiFunction<ExtendedRandom, T, T> randomValue,
|
||||
final BiConsumer<Number, String> verifier,
|
||||
final T... limits
|
||||
) {
|
||||
this.add = add;
|
||||
this.fromLong = fromLong;
|
||||
this.randomValue = randomValue;
|
||||
this.verifier = verifier;
|
||||
this.limits = List.of(limits);
|
||||
|
||||
setSpaces(SPACES);
|
||||
setToString(String::valueOf);
|
||||
|
||||
test(1, "1");
|
||||
test(6, "1", "2", "3");
|
||||
test(1, " 1");
|
||||
test(1, "1 ");
|
||||
test(1, " 1 ");
|
||||
test(12345, " 12345 ");
|
||||
test(60, "010", "020", "030");
|
||||
testSpaces(1368, " 123 456 789 ");
|
||||
test(-1, "-1");
|
||||
test(-6, "-1", "-2", "-3");
|
||||
test(-12345, " -12345 ");
|
||||
testSpaces(-1368, " -123 -456 -789 ");
|
||||
test(1, "+1");
|
||||
test(6, "+1", "+2", "+3");
|
||||
test(12345, " +12345 ");
|
||||
testSpaces(1368, " +123 +456 +789 ");
|
||||
test(0);
|
||||
testSpaces(0, " ", " ");
|
||||
}
|
||||
|
||||
protected SumTester<T> setSpaces(final List<String> spaces) {
|
||||
this.spaces = spaces;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected SumTester<T> addSpaces(final String... spaces) {
|
||||
this.spaces = Stream.concat(this.spaces.stream(), Arrays.stream(spaces)).toList();
|
||||
return this;
|
||||
}
|
||||
|
||||
public SumTester<T> setToString(final Function<T, String> toString) {
|
||||
return setToString((r, n) -> toString.apply(n));
|
||||
}
|
||||
|
||||
public SumTester<T> setToString(final BiFunction<ExtendedRandom, T, String> toString) {
|
||||
this.toString = toString;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected SumTester<T> test(final Function<T, String> toString, final long... input) {
|
||||
return testT(
|
||||
fromLong.apply(LongStream.of(input).sum()),
|
||||
LongStream.of(input).mapToObj(fromLong).map(toString).collect(Collectors.joining(" "))
|
||||
);
|
||||
}
|
||||
|
||||
protected SumTester<T> test(final long result, final String... input) {
|
||||
return testT(fromLong.apply(result), input);
|
||||
}
|
||||
|
||||
protected SumTester<T> testT(final T result, final String... input) {
|
||||
return testT(result, Arrays.asList(input));
|
||||
}
|
||||
|
||||
private SumTester<T> testT(final T result, final List<String> input) {
|
||||
tests.add(checker -> checker.test(result, input));
|
||||
return this;
|
||||
}
|
||||
|
||||
public SumTester<T> testSpaces(final long result, final String... input) {
|
||||
final T res = fromLong.apply(result);
|
||||
tests.add(checker -> spaces.stream()
|
||||
.flatMapToInt(String::chars)
|
||||
.forEach(space -> checker.test(
|
||||
res,
|
||||
Functional.map(Arrays.asList(input), s -> s.replace(' ', (char) space))
|
||||
))
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void test(final String name, final TestCounter counter, final Function<String, Runner> runner) {
|
||||
new Checker(counter, runner.apply(name)).test();
|
||||
}
|
||||
|
||||
private class Checker extends BaseChecker {
|
||||
private final Runner runner;
|
||||
|
||||
public Checker(final TestCounter counter, final Runner runner) {
|
||||
super(counter);
|
||||
this.runner = runner;
|
||||
}
|
||||
|
||||
public void test() {
|
||||
tests.forEach(test -> test.accept(this));
|
||||
|
||||
for (final T limit : limits) {
|
||||
for (int n = 10; n <= 10_000 / TestCounter.DENOMINATOR; n *= 10) {
|
||||
randomTest(n, limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void test(final T result, final List<String> input) {
|
||||
counter.test(() -> {
|
||||
final List<String> out = runner.run(counter, input);
|
||||
Asserts.assertEquals("Single line expected", 1, out.size());
|
||||
verifier.accept(result, out.get(0));
|
||||
});
|
||||
}
|
||||
|
||||
private void randomTest(final int numbers, final T max) {
|
||||
for (final String spaces : spaces) {
|
||||
randomTest(numbers, max, spaces);
|
||||
}
|
||||
}
|
||||
|
||||
private void randomTest(final int numbers, final T max, final String spaces) {
|
||||
final List<T> values = new ArrayList<>();
|
||||
for (int i = 0; i < numbers; i++) {
|
||||
values.add(randomValue.apply(random(), max));
|
||||
}
|
||||
testRandom(values.stream().reduce(fromLong.apply(0), add), values, spaces);
|
||||
}
|
||||
|
||||
private void testRandom(final T result, final List<T> args, final String spaces) {
|
||||
final List<String> spaced = args.stream()
|
||||
.map(n -> toString.apply(random(), n))
|
||||
.map(value -> randomSpace(spaces) + value + randomSpace(spaces))
|
||||
.toList();
|
||||
final List<String> argsList = new ArrayList<>();
|
||||
for (final Iterator<String> i = spaced.listIterator(); i.hasNext(); ) {
|
||||
final StringBuilder next = new StringBuilder(i.next());
|
||||
while (i.hasNext() && random().nextBoolean()) {
|
||||
next.append(randomSpace(spaces)).append(i.next());
|
||||
}
|
||||
argsList.add(next.toString());
|
||||
}
|
||||
test(result, argsList);
|
||||
}
|
||||
|
||||
private String randomSpace(final String spaces) {
|
||||
return random().randomString(spaces);
|
||||
}
|
||||
}
|
||||
}
|
||||
7
java/sum/package-info.java
Normal file
7
java/sum/package-info.java
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Tests for <a href="https://www.kgeorgiy.info/courses/prog-intro/homeworks.html#sum">Sum</a> homework
|
||||
* of <a href="https://www.kgeorgiy.info/courses/prog-intro/">Introduction to Programming</a> course.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
package sum;
|
||||
127
java/wordStat/WordStatChecker.java
Normal file
127
java/wordStat/WordStatChecker.java
Normal file
@@ -0,0 +1,127 @@
|
||||
package wordStat;
|
||||
|
||||
import base.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class WordStatChecker extends BaseChecker {
|
||||
public static final String DASH = "-֊־‒–—―⸗⸚⸺〰゠︱︲﹘﹣-'";
|
||||
public static final String SIMPLE_DELIMITERS = " \t";
|
||||
public static final String ADVANCED_DELIMITERS = " \t!\"#%&()*+,./:;<=>?@[\\]^`{|}~ ¡¦§¨©«¬\u00AD®¯°±²³´¶·¸¹»¼½¾¿×÷˂˃˄˅˒˓˔˕˖˗˘˙˚˛˜˝";
|
||||
public static final String ALL = ExtendedRandom.RUSSIAN + ExtendedRandom.ENGLISH + ExtendedRandom.GREEK + DASH;
|
||||
private static final Pattern PATTERN = Pattern.compile("[^\\p{IsLetter}'\\p{Pd}]+");
|
||||
public static final Runner.Packages RUNNER = Runner.packages("", "wordstat", "wspp");
|
||||
|
||||
private final Function<String[][], ? extends List<? extends Pair<?, ?>>> processor;
|
||||
|
||||
private final MainChecker main;
|
||||
|
||||
private WordStatChecker(
|
||||
final String className,
|
||||
final Function<String[][], ? extends List<? extends Pair<?, ?>>> processor,
|
||||
final TestCounter counter
|
||||
) {
|
||||
super(counter);
|
||||
main = new MainChecker(RUNNER.files(className));
|
||||
this.processor = processor;
|
||||
}
|
||||
|
||||
public static void test(
|
||||
final TestCounter counter,
|
||||
final String className,
|
||||
final Function<String[][], ? extends List<? extends Pair<?, ?>>> processor,
|
||||
final Consumer<WordStatChecker> tests
|
||||
) {
|
||||
tests.accept(new WordStatChecker(className, processor, counter));
|
||||
}
|
||||
|
||||
public void test(final String... lines) {
|
||||
test(PATTERN, lines);
|
||||
}
|
||||
|
||||
public void test(final Pattern pattern, final String... lines) {
|
||||
final String[][] data = Arrays.stream(lines)
|
||||
.map(line -> Arrays.stream(pattern.split(line)).filter(Predicate.not(String::isEmpty)).toArray(String[]::new))
|
||||
.toArray(String[][]::new);
|
||||
test(lines, processor.apply(data));
|
||||
}
|
||||
|
||||
private void randomTest(
|
||||
final int wordLength,
|
||||
final int totalWords,
|
||||
final int wordsPerLine,
|
||||
final int lines,
|
||||
final String chars,
|
||||
final String delimiters,
|
||||
final Function<String[][], List<? extends Pair<?, ?>>> processor
|
||||
) {
|
||||
final String[] words = generateWords(wordLength, totalWords, chars);
|
||||
final String[][] text = generateTest(lines, words, wordsPerLine);
|
||||
test(input(text, delimiters), processor.apply(text));
|
||||
}
|
||||
|
||||
public void randomTest(
|
||||
final int wordLength,
|
||||
final int totalWords,
|
||||
final int wordsPerLine,
|
||||
final int lines,
|
||||
final String chars,
|
||||
final String delimiters
|
||||
) {
|
||||
randomTest(wordLength, totalWords, wordsPerLine, lines, chars, delimiters, processor::apply);
|
||||
}
|
||||
|
||||
private void test(final String[] text, final List<? extends Pair<?, ?>> expected) {
|
||||
final List<String> expectedList = expected.stream()
|
||||
.map(p -> p.first() + " " + p.second())
|
||||
.collect(Collectors.toList());
|
||||
main.testEquals(counter, Arrays.asList(text), expectedList);
|
||||
}
|
||||
|
||||
public void test(final String[][] text, final String delimiters, final List<Pair<String, Integer>> answer) {
|
||||
test(input(text, delimiters), answer);
|
||||
}
|
||||
|
||||
private String[] generateWords(final int wordLength, final int totalWords, final String chars) {
|
||||
final String allChars = chars.chars().anyMatch(Character::isUpperCase)
|
||||
? chars : chars + chars.toUpperCase(Locale.ROOT);
|
||||
return IntStream.range(0, totalWords)
|
||||
.mapToObj(i -> random().randomString(allChars, wordLength / 2, wordLength))
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
private String[][] generateTest(final int lines, final String[] words, final int wordsPerLine) {
|
||||
final String[][] text = new String[lines][];
|
||||
for (int i = 0; i < text.length; i++) {
|
||||
text[i] = new String[random().nextInt(wordsPerLine / 2, wordsPerLine)];
|
||||
for (int j = 0; j < text[i].length; j++) {
|
||||
text[i][j] = random().randomItem(words);
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
private String[] input(final String[][] text, final String delimiters) {
|
||||
final String[] input = new String[text.length];
|
||||
for (int i = 0; i < text.length; i++) {
|
||||
final String[] line = text[i];
|
||||
final StringBuilder sb = new StringBuilder(random().randomString(delimiters));
|
||||
for (final String word : line) {
|
||||
sb.append(word).append(random().randomString(delimiters));
|
||||
}
|
||||
input[i] = sb.toString();
|
||||
}
|
||||
return input;
|
||||
}
|
||||
}
|
||||
70
java/wordStat/WordStatTest.java
Normal file
70
java/wordStat/WordStatTest.java
Normal file
@@ -0,0 +1,70 @@
|
||||
package wordStat;
|
||||
|
||||
import base.Named;
|
||||
import base.Selector;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Tests for <a href="https://www.kgeorgiy.info/courses/prog-intro/homeworks.html#wordstat">Word Statistics</a> homework
|
||||
* of <a href="https://www.kgeorgiy.info/courses/prog-intro/">Introduction to Programming</a> course.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class WordStatTest {
|
||||
// === Base
|
||||
private static final Named<Function<String, Stream<String>>> ID = Named.of("", Stream::of);
|
||||
private static final WordStatTester.Variant BASE = new WordStatTester.Variant("", false, Comparator.comparingInt(p -> 0));
|
||||
|
||||
|
||||
// === 3637
|
||||
public static final int SIZE = 3;
|
||||
private static final WordStatTester.Variant LENGTH = new WordStatTester.Variant("Length", false, Comparator.comparingInt(p -> p.first().length()));
|
||||
private static final Named<Function<String, Stream<String>>> MIDDLE =
|
||||
size("Middle", SIZE * 2 + 1, s -> Stream.of(s.substring(SIZE, s.length() - SIZE)));
|
||||
|
||||
static Named<Function<String, Stream<String>>> size(
|
||||
final String name,
|
||||
final int length,
|
||||
final Function<String, Stream<String>> f
|
||||
) {
|
||||
return Named.of(name, s -> s.length() >= length ? f.apply(s) : Stream.empty());
|
||||
}
|
||||
|
||||
// === 3839
|
||||
private static final Named<Function<String, Stream<String>>> AFFIX = size(
|
||||
"Affix",
|
||||
2,
|
||||
s -> Stream.of(s.substring(0, s.length() / 2), s.substring(s.length() - s.length() / 2))
|
||||
);
|
||||
|
||||
// === 3536
|
||||
private static final Named<Function<String, Stream<String>>> SUFFIX =
|
||||
size("Suffix", 2, s -> Stream.of(s.substring(s.length() - s.length() / 2)));
|
||||
|
||||
// === 4749
|
||||
private static final Named<Function<String, Stream<String>>> PREFIX =
|
||||
size("Prefix", 2, s -> Stream.of(s.substring(0, s.length() / 2)));
|
||||
|
||||
// === Common
|
||||
public static final Selector SELECTOR = new Selector(WordStatTester.class)
|
||||
.variant("Base", BASE.with(ID))
|
||||
.variant("3637", LENGTH.with(MIDDLE))
|
||||
.variant("3839", LENGTH.with(AFFIX))
|
||||
.variant("3435", LENGTH.with(SUFFIX))
|
||||
.variant("3233", LENGTH.with(ID))
|
||||
.variant("4142", LENGTH.with(MIDDLE))
|
||||
.variant("4749", LENGTH.with(PREFIX))
|
||||
|
||||
;
|
||||
|
||||
private WordStatTest() {
|
||||
// Utility class
|
||||
}
|
||||
|
||||
public static void main(final String... args) {
|
||||
SELECTOR.main(args);
|
||||
}
|
||||
}
|
||||
100
java/wordStat/WordStatTester.java
Normal file
100
java/wordStat/WordStatTester.java
Normal file
@@ -0,0 +1,100 @@
|
||||
package wordStat;
|
||||
|
||||
import base.ExtendedRandom;
|
||||
import base.Named;
|
||||
import base.Pair;
|
||||
import base.TestCounter;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class WordStatTester {
|
||||
public static final String PRE_LOWER = chars()
|
||||
.filter(s -> s.toLowerCase(Locale.ROOT).length() == 1)
|
||||
.collect(Collectors.joining());
|
||||
public static final String POST_LOWER = chars()
|
||||
.collect(Collectors.joining())
|
||||
.toLowerCase();
|
||||
|
||||
private WordStatTester() {
|
||||
}
|
||||
|
||||
private static Stream<String> chars() {
|
||||
return IntStream.range(' ', Character.MAX_VALUE)
|
||||
.filter(ch -> !Character.isSurrogate((char) ch))
|
||||
.filter(ch -> Character.getType(ch) != Character.NON_SPACING_MARK)
|
||||
.filter(ch -> Character.getType(ch) != Character.DIRECTIONALITY_NONSPACING_MARK)
|
||||
.mapToObj(Character::toString);
|
||||
}
|
||||
|
||||
/* package-private */ record Variant(String name, boolean reverse, Comparator<Pair<String, Integer>> c) {
|
||||
public Consumer<TestCounter> with(final Named<Function<String, Stream<String>>> split) {
|
||||
return counter -> WordStatChecker.test(
|
||||
counter,
|
||||
"WordStat" + name + split.name(),
|
||||
text -> answer(split.value(), text),
|
||||
checker -> {
|
||||
checker.test("To be, or not to be, that is the question:");
|
||||
checker.test("Monday's child is fair of face.", "Tuesday's child is full of grace.");
|
||||
checker.test("Шалтай-Болтай", "Сидел на стене.", "Шалтай-Болтай", "Свалился во сне.");
|
||||
checker.test(
|
||||
"27 октября — 300-й день григорианскому календарю. До конца года остаётся 65 дней.",
|
||||
"До 15 октября 1582 года — 27 октября по юлианскому календарю, с 15 октября 1582 года — 27 октября по григорианскому календарю.",
|
||||
"В XX и XXI веках соответствует 14 октября по юлианскому календарю[1].",
|
||||
"(c) Wikipedia"
|
||||
);
|
||||
checker.test("23 октября — Всемирный день психического здоровья", "Тема 2025 года: Психическое здоровье на рабочем месте");
|
||||
|
||||
checker.randomTest(3, 10, 10, 3, ExtendedRandom.ENGLISH, WordStatChecker.SIMPLE_DELIMITERS);
|
||||
checker.randomTest(10, 3, 5, 5, ExtendedRandom.RUSSIAN, WordStatChecker.SIMPLE_DELIMITERS);
|
||||
checker.randomTest(4, 10, 10, 3, ExtendedRandom.GREEK, WordStatChecker.SIMPLE_DELIMITERS);
|
||||
checker.randomTest(4, 10, 10, 3, WordStatChecker.DASH, WordStatChecker.SIMPLE_DELIMITERS);
|
||||
checker.randomTest(3, 10, 10, 3, ExtendedRandom.ENGLISH, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
checker.randomTest(10, 3, 5, 5, ExtendedRandom.RUSSIAN, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
checker.randomTest(3, 10, 10, 3, ExtendedRandom.GREEK, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
checker.randomTest(3, 10, 10, 3, WordStatChecker.DASH, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
checker.randomTest(3, 10, 10, 10, WordStatChecker.ALL, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
|
||||
final int d = TestCounter.DENOMINATOR;
|
||||
final int d2 = TestCounter.DENOMINATOR;
|
||||
checker.randomTest(10, 10000 / d, 10, 10, WordStatChecker.ALL, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
checker.randomTest(10, 1, 10, 10, WordStatChecker.ALL, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
checker.randomTest(10, 1000 / d, 100 / d2, 100 / d2, WordStatChecker.ALL, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
checker.randomTest(4, 1000 / d, 10, 3000 / d, WordStatChecker.ALL, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
checker.randomTest(4, 1000 / d, 3000 / d, 10, WordStatChecker.ALL, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
checker.randomTest(10000 / d, 20, 10, 5, WordStatChecker.ALL, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
checker.randomTest(1000000 / d, 2, 2, 1, WordStatChecker.ALL, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
|
||||
checker.test(PRE_LOWER);
|
||||
checker.test(POST_LOWER);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private List<Pair<String, Integer>> answer(final Function<String, Stream<String>> split, final String[][] text) {
|
||||
final List<String> parts = Arrays.stream(text)
|
||||
.flatMap(Arrays::stream)
|
||||
.filter(Predicate.not(String::isEmpty))
|
||||
.flatMap(split)
|
||||
.peek(s -> {assert !s.isBlank();})
|
||||
.collect(Collectors.toList());
|
||||
if (reverse()) {
|
||||
Collections.reverse(parts);
|
||||
}
|
||||
return parts.stream()
|
||||
.collect(Collectors.toMap(String::toLowerCase, v -> 1, Integer::sum, LinkedHashMap::new))
|
||||
.entrySet().stream()
|
||||
.map(Pair::of)
|
||||
.sorted(c)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
}
|
||||
7
java/wordStat/package-info.java
Normal file
7
java/wordStat/package-info.java
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Tests for <a href="https://www.kgeorgiy.info/courses/prog-intro/homeworks.html#wordstat">Word Statistics</a> homework
|
||||
* of <a href="https://www.kgeorgiy.info/courses/prog-intro/">Introduction to Programming</a> course.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
package wordStat;
|
||||
51
java/wspp/WsppTest.java
Normal file
51
java/wspp/WsppTest.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package wspp;
|
||||
|
||||
import base.Named;
|
||||
import base.Selector;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class WsppTest {
|
||||
// === Base
|
||||
private static final Named<Comparator<Map.Entry<String, Integer>>> INPUT = Named.of("", Comparator.comparingInt(e -> 0));
|
||||
private static final Named<IntFunction<IntStream>> ALL = Named.of("", size -> IntStream.range(0, size));
|
||||
private static final Named<WsppTester.Extractor<Object>> WSPP = Named.of("", (r, l, L, g, G) -> g);
|
||||
private static final Named<String> NONE = Named.of("", "");
|
||||
|
||||
// === 3637
|
||||
private static final Named<Comparator<Map.Entry<String, Integer>>> LENGTH = Named.of("",
|
||||
Map.Entry.comparingByKey(Comparator.comparingInt(String::length)));
|
||||
private static final Named<IntFunction<IntStream>> LAST = Named.of("Last", size -> IntStream.of(size - 1));
|
||||
private static final Named<String> JAVA = Named.of("", "XHB7TmR9JF8=");
|
||||
|
||||
// === 3839
|
||||
private static final Named<IntFunction<IntStream>> MIDDLE = Named.of("Middle", size -> IntStream.of(size / 2));
|
||||
|
||||
// === 3435
|
||||
public static final WsppTester.Extractor<String> POSITION = (r, l, L, g, G) -> r + ":" + (G - g + 1);
|
||||
|
||||
|
||||
// === Common
|
||||
public static final Selector SELECTOR = new Selector(WsppTester.class)
|
||||
.variant("Base", WsppTester.variant(INPUT, ALL, WSPP, NONE))
|
||||
.variant("3637", WsppTester.variant(LENGTH, LAST, WSPP, JAVA))
|
||||
.variant("3839", WsppTester.variant(LENGTH, MIDDLE, WSPP, JAVA))
|
||||
.variant("3435", WsppTester.variant(LENGTH, ALL, Named.of("Position", POSITION), JAVA))
|
||||
.variant("3233", WsppTester.variant(INPUT, ALL, Named.of("Pos", POSITION), JAVA))
|
||||
.variant("4142", WsppTester.variant(LENGTH, LAST, WSPP, JAVA))
|
||||
.variant("4749", WsppTester.variant(LENGTH, ALL, Named.of("Position", POSITION), JAVA))
|
||||
;
|
||||
|
||||
private WsppTest() {
|
||||
}
|
||||
|
||||
public static void main(final String... args) {
|
||||
SELECTOR.main(args);
|
||||
}
|
||||
}
|
||||
139
java/wspp/WsppTester.java
Normal file
139
java/wspp/WsppTester.java
Normal file
@@ -0,0 +1,139 @@
|
||||
package wspp;
|
||||
|
||||
import base.ExtendedRandom;
|
||||
import base.Named;
|
||||
import base.Pair;
|
||||
import base.TestCounter;
|
||||
import wordStat.WordStatChecker;
|
||||
import wordStat.WordStatTester;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class WsppTester {
|
||||
private WsppTester() {
|
||||
}
|
||||
|
||||
public static <T> Consumer<TestCounter> variant(
|
||||
final Named<Comparator<Map.Entry<String, Integer>>> comparator,
|
||||
final Named<IntFunction<IntStream>> selector,
|
||||
final Named<Extractor<T>> extractor,
|
||||
final Named<String> extra
|
||||
) {
|
||||
// Stream "magic" code. You do not expect to understand it
|
||||
return counter -> WordStatChecker.test(
|
||||
counter,
|
||||
"Wspp" + comparator.name() + selector.name() + extractor.name() + extra.name(),
|
||||
text -> {
|
||||
final Map<String, Integer> totals = Arrays.stream(text)
|
||||
.flatMap(Arrays::stream)
|
||||
.map(word -> word.toLowerCase(Locale.ROOT))
|
||||
.collect(Collectors.toMap(Function.identity(), k -> 1, Integer::sum, LinkedHashMap::new));
|
||||
final int[] lengths = Arrays.stream(text).mapToInt(a -> a.length).toArray();
|
||||
final int[] sizes = new int[lengths.length + 1];
|
||||
int start = 0;
|
||||
for (int i = 0; i < lengths.length; i++) {
|
||||
sizes[i] = start;
|
||||
start += lengths[i];
|
||||
}
|
||||
sizes[lengths.length] = start;
|
||||
|
||||
final Map<String, String> selected = IntStream.range(0, text.length).boxed()
|
||||
.flatMap(r -> {
|
||||
final String[] line = text[r];
|
||||
return IntStream.range(0, line.length).boxed()
|
||||
.collect(Collectors.groupingBy(
|
||||
w -> line[w].toLowerCase(Locale.ROOT),
|
||||
Collectors.collectingAndThen(
|
||||
Collectors.mapping(
|
||||
w -> extractor.value().select(
|
||||
r + 1,
|
||||
w + 1,
|
||||
line.length,
|
||||
sizes[r] + w + 1,
|
||||
sizes[lengths.length]
|
||||
),
|
||||
Collectors.toUnmodifiableList()
|
||||
),
|
||||
list -> selector.value()
|
||||
.apply(list.size())
|
||||
.mapToObj(list::get)
|
||||
.toList()
|
||||
)
|
||||
))
|
||||
.entrySet().stream();
|
||||
}
|
||||
)
|
||||
.collect(Collectors.groupingBy(
|
||||
Map.Entry::getKey,
|
||||
Collectors.flatMapping(
|
||||
e -> e.getValue().stream(),
|
||||
Collectors.mapping(
|
||||
String::valueOf,
|
||||
Collectors.mapping(" "::concat, Collectors.joining())
|
||||
)
|
||||
)
|
||||
));
|
||||
return totals.entrySet().stream()
|
||||
.sorted(comparator.value())
|
||||
.map(e -> Pair.of(e.getKey(), e.getValue() + selected.get(e.getKey())))
|
||||
.collect(Collectors.toList());
|
||||
},
|
||||
checker -> {
|
||||
final Pattern pattern = Pattern.compile(new String(Base64.getDecoder().decode("W15ccHtJc0xldHRlcn0nXHB7UGR9" + extra.value()), StandardCharsets.US_ASCII) + "]+");
|
||||
final String good = String.join("", pattern.split(WordStatTester.POST_LOWER));
|
||||
|
||||
checker.test(pattern, "To be, or not to be, that is the question:");
|
||||
checker.test(
|
||||
pattern,
|
||||
"Monday's child is fair of face.",
|
||||
"Tuesday's child is full of grace."
|
||||
);
|
||||
checker.test(
|
||||
pattern,
|
||||
"Шалтай-Болтай",
|
||||
"Сидел на стене.",
|
||||
"Шалтай-Болтай",
|
||||
"Свалился во сне."
|
||||
);
|
||||
|
||||
checker.randomTest(3, 10, 10, 3, ExtendedRandom.ENGLISH, WordStatChecker.SIMPLE_DELIMITERS);
|
||||
checker.randomTest(10, 3, 5, 5, ExtendedRandom.RUSSIAN, WordStatChecker.SIMPLE_DELIMITERS);
|
||||
checker.randomTest(3, 10, 10, 3, ExtendedRandom.GREEK, WordStatChecker.SIMPLE_DELIMITERS);
|
||||
checker.randomTest(3, 10, 10, 3, WordStatChecker.DASH, WordStatChecker.SIMPLE_DELIMITERS);
|
||||
checker.randomTest(3, 10, 10, 3, ExtendedRandom.ENGLISH, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
checker.randomTest(10, 3, 5, 5, ExtendedRandom.RUSSIAN, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
checker.randomTest(3, 10, 10, 3, ExtendedRandom.GREEK, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
checker.randomTest(3, 10, 10, 3, WordStatChecker.DASH, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
|
||||
checker.randomTest(10, 20, 10, 3, good, WordStatChecker.SIMPLE_DELIMITERS);
|
||||
checker.randomTest(10, 20, 10, 3, good, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
|
||||
final int d = TestCounter.DENOMINATOR;
|
||||
final int d2 = TestCounter.DENOMINATOR2;
|
||||
checker.randomTest(100, 1000 / d, 1000 / d2, 1000 / d2, WordStatChecker.ALL, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
checker.randomTest(10, 1000 / d, 1000 / d2, 1000 / d2, good, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
|
||||
checker.randomTest(10000 / d, 20, 10, 5, WordStatChecker.ALL, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
checker.randomTest(1000000 / d, 2, 2, 1, WordStatChecker.ALL, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
|
||||
checker.test(pattern, WordStatTester.PRE_LOWER);
|
||||
checker.test(pattern, WordStatTester.POST_LOWER);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Extractor<T> {
|
||||
T select(int l, int li, int lt, int gi, int gt);
|
||||
}
|
||||
}
|
||||
7
java/wspp/package-info.java
Normal file
7
java/wspp/package-info.java
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Tests for <a href="https://www.kgeorgiy.info/courses/prog-intro/homeworks.html#wspp">Word Statistics++</a> homework
|
||||
* of <a href="https://www.kgeorgiy.info/courses/prog-intro/">Introduction to Programming</a> course.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
package wspp;
|
||||
113
test-rules.md
Normal file
113
test-rules.md
Normal file
@@ -0,0 +1,113 @@
|
||||
---
|
||||
gitea: none
|
||||
include_toc: true
|
||||
---
|
||||
|
||||
# Зачёт по дисциплине «Введение в программирование»
|
||||
|
||||
## Расписание
|
||||
|
||||
Досрочная сдача зачёта:
|
||||
|
||||
* M3132-35: 30 декабря с 11:30, ауд. 2137
|
||||
* M3136-39: 30 декабря с 11:30, ауд. 2137
|
||||
|
||||
Сдача зачёта в сессию:
|
||||
|
||||
* M3132-35: 10 января с 11:30, ауд. 1229
|
||||
* M3136-39: 20 января с 11:30, ауд. 2137
|
||||
|
||||
|
||||
Пересдача зачёта пройдёт:
|
||||
|
||||
* M3132-39: TBA
|
||||
* M3136-39: TBA
|
||||
|
||||
|
||||
## Сдача зачёта
|
||||
|
||||
Если вас устраивают ваши баллы, то зачёт можно не сдавать.
|
||||
Вы можете сдавать зачёт либо досрочно, либо во время сессии, по вашему выбору.
|
||||
|
||||
Для того, чтобы записаться на сдачу зачёта надо заполнить
|
||||
[форму](https://docs.google.com/forms/d/e/1FAIpQLScjft8hZCjlfoeVicSJHnX_uMW7xpA5RxSMQwqhy6aXGZWCGw/viewform).
|
||||
Для заполнения вам потребуются логин и пароль от репозитория.
|
||||
Если не указано обратного, то на сдачу надо записаться до 9:00 дня сдачи.
|
||||
Если вы не записались на сдачу вовремя, то вы можете прийти на зачёт,
|
||||
но вы будете обработаны в ручном режиме после решения проблем всех записавшихся вовремя.
|
||||
Время на обработку компенсировано не будет.
|
||||
|
||||
Если у вас больше 59 баллов, то их округлят до 60 если вы заполните форму
|
||||
и пообещаете не сдавать зачёт (это требуется отметить в форме).
|
||||
|
||||
|
||||
## Формат заданий
|
||||
|
||||
Вам будет выдано задание, связанное с пройденными темами.
|
||||
В условии описано, что вам надо сделать и набор требований,
|
||||
которым должно удовлетворять решение (примерно как в домашних заданиях).
|
||||
|
||||
Некоторые задания рассчитаны на то, что вы адаптируте или скомпануете
|
||||
код одного или нескольких ДЗ.
|
||||
Если вы не сдали соответствующие ДЗ, то вы можете написать весь код с нуля,
|
||||
но это будет сложнее.
|
||||
|
||||
Если вам что-то не ясно в задании (например, оно кажется подозрительно простым),
|
||||
то вы можете задать вопрос по условию.
|
||||
Принимаются только вопросы о том, что надо сделать.
|
||||
Как именно делать остаётся на ваше усмотрение.
|
||||
|
||||
По решению должно быть очевидно как его запускать.
|
||||
Например, назвать основной класс `Main` или по имени задания —
|
||||
хорошая идея, а `FooBazFactoryProvider` — нет.
|
||||
|
||||
Если вы хотете передать проверяющему какую-то дополнительную информацию
|
||||
(например, о выбранном компромиссе между памятью и временем исполнения),
|
||||
то напишите её либо в комментариях к коду, либо положите рядом с кодом
|
||||
`README.md`.
|
||||
|
||||
В решении вы можете использовать ваш код из ДЗ и код,
|
||||
написанный преподавателями на лекциях.
|
||||
|
||||
|
||||
## Порядок сдачи
|
||||
|
||||
1. Заранее проверьте, что у вас работает всё, необходимое для сдачи.
|
||||
Претензии вида «у меня не работал компилятор/IDE/git/браузер/интернет» не принимаются.
|
||||
1. За 10 минут до начала зачёта сбор студентов в соответствующей аудитории.
|
||||
Позаботьтесь, что бы вам хватило заряда ноутбука или принесите с собой удлинитель-тройник.
|
||||
1. Организационные вопросы.
|
||||
В том числе, можно отказаться от участия, если записались по ошибке,
|
||||
или вам больше не актуально.
|
||||
1. В момент *T* объявляется начало и выдаются билеты с заданиями.
|
||||
1. Ответы на вопросы по билетам до *T*+30 минут.
|
||||
1. Вы пишите решение и записываете его в каталог `java-solutions` зачётного репозитория
|
||||
`https://www.kgeorgiy.info/git-students/year2025/<USER>/prog-intro-exam`.
|
||||
1. В момент времени *T*+3 часа фиксируется состояние репозиториев.
|
||||
1. Проверяется код на состояние *T*+3 часа. Это может занять несколько дней.
|
||||
1. Результаты проверки отображаются в табличке,
|
||||
комментарии по проверке загружаются в репозитории.
|
||||
|
||||
|
||||
## Система оценки
|
||||
|
||||
В репозитории есть код, решающий поставленную задачу (возможно не всю):
|
||||
`20` баллов минус баллы за проблемы:
|
||||
|
||||
* `#` — большая проблема (обычно не выполнено одно из требований задания): `−5` баллов
|
||||
* `*` — средняя проблема (обычно нарушение неоднократно обсуждавшихся рекомендаций,
|
||||
например, утечка ресурсов, если это не является основной целью задания): `−2` балла
|
||||
* `-` — маленькая проблема (например, однократное нарушение правил оформления кода): `−1` балл
|
||||
|
||||
Код (почти) отсутствует/не имеет отношения к заданию, преподаватель не нашёл ваш код: `−5` баллов
|
||||
|
||||
Код не компилируется/синтаксически некорректен: `−10` баллов, дальнейшая проверка не осуществляется.
|
||||
|
||||
Код списан: `−20` баллов
|
||||
|
||||
|
||||
## Полезные ссылки
|
||||
|
||||
1. [Форма для записи на зачет](https://docs.google.com/forms/d/e/1FAIpQLScjft8hZCjlfoeVicSJHnX_uMW7xpA5RxSMQwqhy6aXGZWCGw/viewform)
|
||||
1. [Результаты записи на зачет](https://docs.google.com/spreadsheets/d/1g1XA_62KxWQHjXHsGoEAg0nisYQGZ_amLPVWXH_ftEA/edit?gid=1533773578#gid=1533773578)
|
||||
1. Экзаменационный репозиторий: `https://www.kgeorgiy.info/git-students/year2025/<USER>/prog-intro-exam`
|
||||
Reference in New Issue
Block a user