initial commit

This commit is contained in:
2026-01-29 23:20:12 +05:00
parent fb1ce36970
commit 67357cf271
76 changed files with 7115 additions and 0 deletions

550
java/RunMe.java Normal file
View 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
View 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);
}
}

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

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

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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";
}
}

View 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;

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

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

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

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

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

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

View File

@@ -0,0 +1,10 @@
package expression;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public interface ToMiniString {
default String toMiniString() {
return toString();
}
}

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

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

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

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

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

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

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

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

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

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

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

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

View 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;

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

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

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

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

View 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;

View 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;

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

View 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> {}
}

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

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

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

View 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;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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

View 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;

View 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>Это не &lt;&lt;вставка&gt;&gt;</p>", "<", ">")
.spoiled("Это не <<вставка>> 2", "<p>Это не &lt;&lt;вставка&gt;&gt; 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("<", "&lt;").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);
}
}

View 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("<", "&lt;", ">", "&gt;");
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Код представляется элементом `code`.\n\n",
"<p>Мы все любим <em>выделять</em> текст <em>разными</em> способами.\n<strong>Сильное выделение</strong>, используется гораздо реже,\о <strong>почему бы и нет</strong>?\nНемного <s>зачеркивания</s> еще никому не вредило.\nКод представляется элементом <code>code</code>.</p>"
);
test(
"Обратите внимание, как экранируются специальные\nHTML-символы, такие как `<`, `>` и `&`.\n\n",
"<p>Обратите внимание, как экранируются специальные\nHTML-символы, такие как <code>&lt;</code>, <code>&gt;</code> и <code>&amp;</code>.</p>"
);
test(
"Экранирование должно работать во всех местах: <>&.\n\n",
"<p>Экранирование должно работать во всех местах: &lt;&gt;&amp;.</p>"
);
test(
"Знаете ли вы, что в Markdown, одиночные * и _\е означают выделение?\nОни так же могут быть заэкранированы\nпри помощи обратного слэша: \\*.",
"<p>Знаете ли вы, что в Markdown, одиночные * и _\е означают выделение?\nОни так же могут быть заэкранированы\nпри помощи обратного слэша: *.</p>"
);
test(
"\n\n\nЛишние пустые строки должны игнорироваться.\n\n\n\n",
"<p>Лишние пустые строки должны игнорироваться.</p>"
);
test(
"Любите ли вы *вложенные __выделения__* так,\ак __--люблю--__ их я?",
"<p>Любите ли вы <em>вложенные <strong>выделения</strong></em> так,\ак <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>&lt;</code>, <code>&gt;</code> и <code>&amp;</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);
}
}

View 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;

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

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

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

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

View 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;

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

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

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

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

View 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;