update
All checks were successful
Markup Tests / test (push) Successful in 8s
Markdown to Html Tests / test (push) Successful in 17s

This commit is contained in:
2026-02-17 09:32:08 +03:00
commit 2f05f238e9
109 changed files with 9369 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,47 @@
package markup;
import java.util.List;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public abstract class AbstractList implements ContainsInListItem {
private final List<ListItem> items;
private final String highlight;
private final String texBegin;
private final String texEnd;
protected AbstractList(
List<ListItem> items,
String highlight,
String texBegin,
String texEnd
) {
this.items = items;
this.highlight = highlight;
this.texBegin = texBegin;
this.texEnd = texEnd;
}
@Override
public void toHtml(StringBuilder sb) {
sb.append("<").append(highlight).append(">");
for (ListItem item : items) {
item.toHtml(sb);
}
sb.append("</").append(highlight).append(">");
}
@Override
public void toMarkdown(StringBuilder sb) {}
@Override
public void toTex(StringBuilder sb) {
sb.append(texBegin);
for (ListItem item : items) {
item.toTex(sb);
}
sb.append(texEnd);
}
}

View File

@@ -0,0 +1,66 @@
package markup;
import java.util.List;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public abstract class AbstractMarkup implements Markdown, Html, Tex {
protected final List<? extends Markup> items;
private final String highlightMarkdown;
private final String highlightHtml;
private final String highlightTexOpen;
private final String highlightTexClose;
protected AbstractMarkup(
List<? extends Markup> items,
String highlightMarkdown,
String highlightHtml,
String highlightTexOpen,
String highlightTexClose
) {
this.items = items;
this.highlightMarkdown = highlightMarkdown;
this.highlightHtml = highlightHtml;
this.highlightTexOpen = highlightTexOpen;
this.highlightTexClose = highlightTexClose;
}
@Override
public void toMarkdown(StringBuilder sb) {
sb.append(highlightMarkdown);
for (Markup item : items) {
item.toMarkdown(sb);
}
sb.append(highlightMarkdown);
}
@Override
public void toHtml(StringBuilder sb) {
if (!highlightHtml.isEmpty()) {
sb.append("<").append(highlightHtml).append(">");
}
for (Markup item : items) {
item.toHtml(sb);
}
if (!highlightHtml.isEmpty()) {
sb.append("</").append(highlightHtml).append(">");
}
}
@Override
public void toTex(StringBuilder sb) {
sb.append(highlightTexOpen);
for (Markup item : items) {
if (item instanceof Text) {
((Text) item).toTex(sb);
} else if (item instanceof AbstractMarkup) {
((AbstractMarkup) item).toTex(sb);
} else if (item instanceof AbstractList) {
((AbstractList) item).toTex(sb);
}
}
sb.append(highlightTexClose);
}
}

View File

@@ -0,0 +1,6 @@
package markup;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public interface ContainsInListItem extends Markup {}

13
java/markup/Emphasis.java Normal file
View File

@@ -0,0 +1,13 @@
package markup;
import java.util.List;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class Emphasis extends AbstractMarkup implements PartOfParagraph {
public Emphasis(List<PartOfParagraph> items) {
super(items, "*", "em", "\\emph{", "}");
}
}

8
java/markup/Html.java Normal file
View File

@@ -0,0 +1,8 @@
package markup;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public interface Html {
void toHtml(StringBuilder sb);
}

13
java/markup/ListItem.java Normal file
View File

@@ -0,0 +1,13 @@
package markup;
import java.util.List;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class ListItem extends AbstractMarkup implements Markup {
public ListItem(List<ContainsInListItem> items) {
super(items, "", "li", "\\item ", "");
}
}

View File

@@ -0,0 +1,8 @@
package markup;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public interface Markdown {
void toMarkdown(StringBuilder sb);
}

6
java/markup/Markup.java Normal file
View File

@@ -0,0 +1,6 @@
package markup;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public interface Markup extends Markdown, Html, Tex {}

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,13 @@
package markup;
import java.util.List;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class OrderedList extends AbstractList {
public OrderedList(List<ListItem> items) {
super(items, "ol", "\\begin{enumerate}", "\\end{enumerate}");
}
}

View File

@@ -0,0 +1,16 @@
package markup;
import java.util.List;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class Paragraph
extends AbstractMarkup
implements ContainsInListItem, PrimePart
{
public Paragraph(List<PartOfParagraph> items) {
super(items, "", "p", "\\par{}", "");
}
}

View File

@@ -0,0 +1,6 @@
package markup;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public interface PartOfParagraph extends Markup {}

View File

@@ -0,0 +1,6 @@
package markup;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public interface PrimePart extends Markup {}

View File

@@ -0,0 +1,13 @@
package markup;
import java.util.List;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class Strikeout extends AbstractMarkup implements PartOfParagraph {
public Strikeout(List<PartOfParagraph> items) {
super(items, "~", "s", "\\textst{", "}");
}
}

13
java/markup/Strong.java Normal file
View File

@@ -0,0 +1,13 @@
package markup;
import java.util.List;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class Strong extends AbstractMarkup implements PartOfParagraph {
public Strong(List<PartOfParagraph> items) {
super(items, "__", "strong", "\\textbf{", "}");
}
}

8
java/markup/Tex.java Normal file
View File

@@ -0,0 +1,8 @@
package markup;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public interface Tex {
void toTex(StringBuilder sb);
}

27
java/markup/Text.java Normal file
View File

@@ -0,0 +1,27 @@
package markup;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class Text implements PartOfParagraph {
private final String text;
public Text(String text) {
this.text = text;
}
@Override
public void toHtml(StringBuilder sb) {
sb.append(text);
}
@Override
public void toMarkdown(StringBuilder sb) {
sb.append(text);
}
public void toTex(StringBuilder sb) {
sb.append(text);
}
}

View File

@@ -0,0 +1,13 @@
package markup;
import java.util.List;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class UnorderedList extends AbstractList {
public UnorderedList(List<ListItem> items) {
super(items, "ul", "\\begin{itemize}", "\\end{itemize}");
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 88 KiB

View File

@@ -0,0 +1,157 @@
classDiagram
%% Интерфейсы
class Markdown {
<<interface>>
+toMarkdown(StringBuilder)
}
class Html {
<<interface>>
+toHtml(StringBuilder)
}
class Markup {
<<interface>>
+toMarkdown(StringBuilder)
+toHtml(StringBuilder)
}
class PartOfParagraph {
<<interface>>
+toMarkdown(StringBuilder)
+toHtml(StringBuilder)
}
class ContainsInListItem {
<<interface>>
+toMarkdown(StringBuilder)
+toHtml(StringBuilder)
}
class PrimePart {
<<interface>>
+toMarkdown(StringBuilder)
+toHtml(StringBuilder)
}
%% Наследование интерфейсов
Markup --|> Markdown
Markup --|> Html
PartOfParagraph --|> Markup
ContainsInListItem --|> Markup
PrimePart --|> Markup
%% Абстрактные классы
class AbstractMarkup {
<<abstract>>
#List~Markup~ items
-String highlightMarkdown
-String highlightHtml
-String highlightTexOpen
-String highlightTexClose
+AbstractMarkup(List, String, String, String, String)
+toMarkdown(StringBuilder)
+toHtml(StringBuilder)
+toTex(StringBuilder)
}
class AbstractList {
<<abstract>>
-List~ListItem~ items
-String highlight
-String texBegin
-String texEnd
+AbstractList(List, String, String, String)
+toHtml(StringBuilder)
+toMarkdown(StringBuilder)
+toTex(StringBuilder)
}
%% Абстрактные классы реализуют интерфейсы
AbstractMarkup ..|> Markdown
AbstractMarkup ..|> Html
AbstractList ..|> ContainsInListItem
%% Inline элементы
class Text {
-String text
+Text(String)
+toMarkdown(StringBuilder)
+toHtml(StringBuilder)
+toTex(StringBuilder)
}
class Emphasis {
+Emphasis(List~PartOfParagraph~)
}
class Strong {
+Strong(List~PartOfParagraph~)
}
class Strikeout {
+Strikeout(List~PartOfParagraph~)
}
%% Inline элементы наследуются и реализуют
Text ..|> PartOfParagraph
Emphasis --|> AbstractMarkup
Emphasis ..|> PartOfParagraph
Strong --|> AbstractMarkup
Strong ..|> PartOfParagraph
Strikeout --|> AbstractMarkup
Strikeout ..|> PartOfParagraph
%% Paragraph
class Paragraph {
+Paragraph(List~PartOfParagraph~)
}
Paragraph --|> AbstractMarkup
Paragraph ..|> ContainsInListItem
Paragraph ..|> PrimePart
%% Списки
class OrderedList {
+OrderedList(List~ListItem~)
}
class UnorderedList {
+UnorderedList(List~ListItem~)
}
class ListItem {
+ListItem(List~ContainsInListItem~)
}
OrderedList --|> AbstractList
UnorderedList --|> AbstractList
ListItem --|> AbstractMarkup
ListItem ..|> Markup
%% Зависимости (что может содержать что)
AbstractMarkup o-- Markup : contains
AbstractList o-- ListItem : contains
Emphasis o-- PartOfParagraph : contains
Strong o-- PartOfParagraph : contains
Strikeout o-- PartOfParagraph : contains
Paragraph o-- PartOfParagraph : contains
ListItem o-- ContainsInListItem : contains
%% Стили
style Markdown fill:#e1f5ff
style Html fill:#e1f5ff
style Markup fill:#e1f5ff
style PartOfParagraph fill:#fff4e1
style ContainsInListItem fill:#fff4e1
style PrimePart fill:#fff4e1
style AbstractMarkup fill:#ffe1f5
style AbstractList fill:#ffe1f5
style Text fill:#e1ffe1
style Emphasis fill:#e1ffe1
style Strong fill:#e1ffe1
style Strikeout fill:#e1ffe1
style Paragraph fill:#ffe1e1
style OrderedList fill:#f5e1ff
style UnorderedList fill:#f5e1ff
style ListItem fill:#f5e1ff

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,61 @@
package md2html;
import java.util.ArrayList;
import java.util.List;
public class BlockCreator {
private final String text;
private final List<String> blocks;
public BlockCreator(String text) {
this.text = text;
blocks = new ArrayList<>();
}
public List<String> divideByBlocks() {
StringBuilder sb = new StringBuilder();
int i = 0;
while (i < text.length()) {
if (isNewLine(text.charAt(i))) {
i = newLine(i);
if (i < text.length() && isNewLine(text.charAt(i))) {
i = newLine(i);
addToBlock(sb);
} else if (i < text.length()){
if (!sb.isEmpty()) {
sb.append(System.lineSeparator());
}
sb.append(text.charAt(i++));
}
} else {
sb.append(text.charAt(i++));
}
}
addToBlock(sb);
return blocks;
}
private void addToBlock(StringBuilder item) {
if (!item.isEmpty()) {
blocks.add(item.toString());
item.setLength(0);
}
}
private int newLine(int i) {
if (i < text.length() && text.charAt(i) == '\r') {
i++;
if (i < text.length() && text.charAt(i) == '\n') {
i++;
}
} else {
i++;
}
return i;
}
private static boolean isNewLine(char ch) {
return (ch == '\u2028') || (ch == '\u2029') ||
(ch == '\u0085') || (ch == '\n') || (ch == '\r');
}
}

11
java/md2html/Code.java Normal file
View File

@@ -0,0 +1,11 @@
package md2html;
import java.util.List;
import markup.*;
public class Code extends AbstractMarkup implements PartOfParagraph {
public Code(List<PartOfParagraph> items) {
super(items, "'", "code", "", "");
}
}

14
java/md2html/Header.java Normal file
View File

@@ -0,0 +1,14 @@
package md2html;
import java.util.List;
import markup.*;
public class Header extends AbstractMarkup implements PrimePart {
public Header(List<PartOfParagraph> items, int level) {
super(items,
"#".repeat(level),
"h" + level,
"",
"");
}
}

64
java/md2html/Md2Html.java Normal file
View File

@@ -0,0 +1,64 @@
package md2html;
import markup.*;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
public class Md2Html {
public static void main(String[] args) {
String text = readFile(args[0]);
BlockCreator creator = new BlockCreator(text);
List<String> blocks = creator.divideByBlocks();
List<PrimePart> parts = new ArrayList<>();
for (String block : blocks) {
PrimePartCreator creatorPrime = new PrimePartCreator(block);
parts.add(creatorPrime.createPart());
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < parts.size(); i++) {
parts.get(i).toHtml(sb);
if (i < parts.size() - 1) {
sb.append(System.lineSeparator());
}
}
writeToFile(args[1], sb.toString());
}
public static String readFile(String nameOfFile) {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream(nameOfFile), StandardCharsets.UTF_8
)
)) {
StringBuilder text = new StringBuilder();
int read = reader.read();
while (read != -1) {
text.append((char) read);
read = reader.read();
}
return text.toString();
} catch (FileNotFoundException e) {
System.out.println("Input file not found: " + e.getMessage());
} catch (IOException e) {
System.out.println("IOException file not found: " + e.getMessage());
}
return null;
}
public static void writeToFile(String nameOfFile, String text) {
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(nameOfFile), StandardCharsets.UTF_8
)
)) {
writer.write(text);
} catch (FileNotFoundException e) {
System.out.println("Output file not found: " + e.getMessage());
} catch (IOException e) {
System.out.println("IOException file not found: " + e.getMessage());
}
}
}

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,253 @@
package md2html;
import java.util.ArrayList;
import java.util.List;
import markup.*;
public class PrimePartCreator {
private final String block;
private int currentChar;
public enum MarkdownToken {
WORD,
EMPHASIS_STAR,
STRONG_STAR,
EMPHASIS_UNDERLINE,
STRONG_UNDERLINE,
STRIKEOUT,
CODE,
SCREENING,
NOTHING,
}
public PrimePartCreator(String block) {
this.block = block;
currentChar = 0;
}
public PrimePart createPart() {
PrimePart result;
if (isHeader()) {
int levelOfHeader = 0;
while (
levelOfHeader < block.length() &&
block.charAt(levelOfHeader) == '#'
) {
levelOfHeader++;
}
currentChar = levelOfHeader + 1;
result = new Header(buildPart(MarkdownToken.WORD), levelOfHeader);
} else {
currentChar = 0;
result = new Paragraph(buildPart(MarkdownToken.WORD));
}
return result;
}
private List<PartOfParagraph> buildPart(MarkdownToken currentToken) {
List<PartOfParagraph> items = new ArrayList<>();
MarkdownToken token = nextMarkdownToken();
StringBuilder sb = new StringBuilder();
while (token != MarkdownToken.NOTHING) {
if (
(token == currentToken && currentToken != MarkdownToken.WORD) ||
isSuffix(currentToken)
) {
addToList(items, sb);
if (
!(token == currentToken &&
currentToken != MarkdownToken.WORD)
) {
currentChar++;
}
break;
}
switch (token) {
case STRIKEOUT -> {
addToList(items, sb);
Strikeout strikeout = new Strikeout(buildPart(token));
items.add(strikeout);
}
case STRONG_STAR, STRONG_UNDERLINE -> {
addToList(items, sb);
Strong strong = new Strong(buildPart(token));
items.add(strong);
}
case EMPHASIS_STAR, EMPHASIS_UNDERLINE -> {
addToList(items, sb);
Emphasis emphasis = new Emphasis(buildPart(token));
items.add(emphasis);
}
case SCREENING -> {
addToList(items, sb);
items.add(
new Text(String.valueOf(block.charAt(currentChar)))
);
currentChar++;
}
case CODE -> {
addToList(items, sb);
Code code = new Code(buildPart(token));
items.add(code);
}
default -> {
if (currentChar < block.length()) {
if (block.charAt(currentChar) == '<') {
sb.append("&lt;");
} else if (block.charAt(currentChar) == '>') {
sb.append("&gt;");
} else if (block.charAt(currentChar) == '&') {
sb.append("&amp;");
} else {
sb.append(block.charAt(currentChar));
}
currentChar++;
}
}
}
token = nextMarkdownToken();
}
if (token == MarkdownToken.NOTHING) {
addToList(items, sb);
}
return items;
}
private MarkdownToken nextMarkdownToken() {
if (currentChar == block.length()) {
return MarkdownToken.NOTHING;
}
switch (block.charAt(currentChar)) {
case '*' -> {
if (isPrefix("**")) {
currentChar += 2;
return MarkdownToken.STRONG_STAR;
} else if (isPrefix("*")) {
currentChar += 1;
return MarkdownToken.EMPHASIS_STAR;
}
}
case '_' -> {
if (isPrefix("__")) {
currentChar += 2;
return MarkdownToken.STRONG_UNDERLINE;
} else if (isPrefix("_")) {
currentChar += 1;
return MarkdownToken.EMPHASIS_UNDERLINE;
}
}
case '-' -> {
if (isPrefix("--")) {
currentChar += 2;
return MarkdownToken.STRIKEOUT;
}
}
case '`' -> {
if (isPrefix("`")) {
currentChar += 1;
return MarkdownToken.CODE;
}
}
case '\\' -> {
if (isScreening(block.charAt(currentChar + 1))) {
currentChar++;
return MarkdownToken.SCREENING;
}
}
default -> {
return MarkdownToken.WORD;
}
}
return MarkdownToken.WORD;
}
private static boolean isScreening(char ch) {
return ch == '*' || ch == '_';
}
private boolean isSuffix(MarkdownToken token) {
if (token == MarkdownToken.WORD) {
return false;
}
String suffix = getHighlight(token);
if (isWordInBlock(suffix, currentChar, currentChar + suffix.length())) {
currentChar += suffix.length() - 1;
return true;
}
return false;
}
private boolean isPrefix(String prefix) {
int i = currentChar + prefix.length();
return (
isValidIndexInBlock(i + prefix.length()) &&
!isWordInBlock(prefix, i, i + prefix.length()) &&
isWordInBlock(prefix, i - prefix.length(), i) &&
!Character.isWhitespace(block.charAt(i))
);
}
private boolean isWordInBlock(String word, int start, int end) {
return word.equals(block.substring(start, end));
}
private boolean isValidIndexInBlock(int index) {
return index < block.length();
}
private boolean isHeader() {
int levelOfHeader = 0;
if (block.charAt(levelOfHeader) != '#') {
return false;
}
while (
levelOfHeader < block.length() && block.charAt(levelOfHeader) == '#'
) {
levelOfHeader++;
}
return (
levelOfHeader < block.length() &&
Character.isWhitespace(block.charAt(levelOfHeader))
);
}
private static String getHighlight(MarkdownToken token) {
switch (token) {
case STRONG_STAR -> {
return "**";
}
case STRONG_UNDERLINE -> {
return "__";
}
case EMPHASIS_STAR -> {
return "*";
}
case EMPHASIS_UNDERLINE -> {
return "_";
}
case CODE -> {
return "`";
}
case STRIKEOUT -> {
return "--";
}
case SCREENING -> {
return "\\";
}
default -> {
return "";
}
}
}
private static void addToList(
List<PartOfParagraph> items,
StringBuilder sb
) {
if (!sb.isEmpty()) {
items.add(new Text(sb.toString()));
sb.setLength(0);
}
}
}

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,58 @@
package reverse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class FastScanner {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = null;
int pos = 0;
boolean hasNextLine() throws IOException {
if (line != null && pos < line.length()) return true;
line = br.readLine();
pos = 0;
return line != null;
}
boolean hasNextInt() {
if (line == null) return false;
while (pos < line.length() && (Character.isWhitespace(line.charAt(pos)) ||
Character.getType(line.charAt(pos)) == Character.START_PUNCTUATION ||
Character.getType(line.charAt(pos)) == Character.END_PUNCTUATION)) {
pos++;
}
return pos < line.length() && (Character.isDigit(line.charAt(pos)) || line.charAt(pos) == '-');
}
int nextInt() {
while (pos < line.length() && (Character.isWhitespace(line.charAt(pos)) ||
Character.getType(line.charAt(pos)) == Character.START_PUNCTUATION ||
Character.getType(line.charAt(pos)) == Character.END_PUNCTUATION)) {
pos++;
}
int start = pos;
boolean negative = line.charAt(pos) == '-';
if (negative) pos++;
while (pos < line.length() && Character.isDigit(line.charAt(pos))) {
pos++;
}
int result = 0;
for (int i = negative ? start + 1 : start; i < pos; i++) {
result = result * 10 + (line.charAt(i) - '0');
}
return negative ? -result : result;
}
void nextLine() {
pos = line.length();
}
}

47
java/reverse/Reverse.java Normal file
View File

@@ -0,0 +1,47 @@
package reverse;
import java.io.*;
import java.util.Arrays;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class Reverse {
public static void main(String[] args) throws IOException {
FastScanner sc = new FastScanner();
int[][] lines = new int[8][];
int linesCount = 0;
while (sc.hasNextLine()) {
int[] line = new int[8];
int count = 0;
while (sc.hasNextInt()) {
if (count >= line.length) {
line = Arrays.copyOf(line, line.length * 2);
}
line[count++] = sc.nextInt();
}
sc.nextLine();
line = Arrays.copyOf(line, count);
if (linesCount >= lines.length) {
lines = Arrays.copyOf(lines, lines.length * 2);
}
lines[linesCount++] = line;
}
PrintWriter out = new PrintWriter(System.out);
for (int i = linesCount - 1; i >= 0; i--) {
int[] line = lines[i];
for (int j = line.length - 1; j >= 0; j--) {
if (j < line.length - 1) out.print(" ");
out.print(line[j]);
}
out.println();
}
out.flush();
}
}

View File

@@ -0,0 +1,71 @@
package reverse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class ReverseAvg {
public static void main(String[] args) throws IOException {
FastScanner sc = new FastScanner();
int[][] lines = new int[8][];
int linesCount = 0;
while (sc.hasNextLine()) {
int[] line = new int[8];
int count = 0;
while (sc.hasNextInt()) {
if (count >= line.length) {
line = Arrays.copyOf(line, line.length * 2);
}
line[count++] = sc.nextInt();
}
sc.nextLine();
line = Arrays.copyOf(line, count);
if (linesCount >= lines.length) {
lines = Arrays.copyOf(lines, lines.length * 2);
}
lines[linesCount++] = line;
}
printResult(linesCount, lines);
}
private static void printResult(int linesCount, int[][] lines) {
PrintWriter out = new PrintWriter(System.out);
for (int i = 0; i < linesCount; i++) {
int[] line = lines[i];
for (int j = 0; j < line.length; j++) {
if (j > 0) out.print(" ");
long sum = 0;
long count = 0;
for (int m = 0; m < lines[i].length; m++) {
sum += lines[i][m];
count++;
}
for (int k = 0; k < linesCount; k++) {
if (k != i && lines[k].length > j) {
sum += lines[k][j];
count++;
}
}
out.print(sum / count);
}
out.println();
}
out.flush();
}
}

View File

@@ -0,0 +1,50 @@
package reverse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class ReverseEven {
public static void main(String[] args) throws IOException {
FastScanner sc = new FastScanner();
int[][] lines = new int[8][];
int linesCount = 0;
while (sc.hasNextLine()) {
int[] line = new int[8];
int count = 0;
while (sc.hasNextInt()) {
if (count >= line.length) {
line = Arrays.copyOf(line, line.length * 2);
}
line[count++] = sc.nextInt();
}
sc.nextLine();
line = Arrays.copyOf(line, count);
if (linesCount >= lines.length) {
lines = Arrays.copyOf(lines, lines.length * 2);
}
lines[linesCount++] = line;
}
PrintWriter out = new PrintWriter(System.out);
for (int i = linesCount - 1; i >= 0; i--) {
int[] line = lines[i];
for (int j = line.length - 1; j >= 0; j--) {
if ((i + j) % 2 == 0) {
if (j < line.length - 1) out.print(" ");
out.print(line[j]);
}
}
out.println();
}
out.flush();
}
}

View File

@@ -0,0 +1,66 @@
package reverse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class ReverseMax {
public static void main(String[] args) throws IOException {
FastScanner sc = new FastScanner();
int[][] lines = new int[8][];
int linesCount = 0;
while (sc.hasNextLine()) {
int[] line = new int[8];
int count = 0;
while (sc.hasNextInt()) {
if (count >= line.length) {
line = Arrays.copyOf(line, line.length * 2);
}
line[count++] = sc.nextInt();
}
sc.nextLine();
line = Arrays.copyOf(line, count);
if (linesCount >= lines.length) {
lines = Arrays.copyOf(lines, lines.length * 2);
}
lines[linesCount++] = line;
}
PrintWriter out = getPrintWriter(linesCount, lines);
out.flush();
}
private static PrintWriter getPrintWriter(int linesCount, int[][] lines) {
PrintWriter out = new PrintWriter(System.out);
for (int i = 0; i < linesCount; i++) {
int[] line = lines[i];
for (int j = 0; j < line.length; j++) {
if (j > 0) out.print(" ");
int maxValue = lines[i][j];
for (int k = i; k < linesCount; k++) {
for (int m = j; m < lines[k].length; m++) {
if (lines[k][m] > maxValue) {
maxValue = lines[k][m];
}
}
}
out.print(maxValue);
}
out.println();
}
return out;
}
}

View File

@@ -0,0 +1,65 @@
package reverse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class ReverseMaxC {
public static void main(String[] args) throws IOException {
FastScanner sc = new FastScanner();
int[][] lines = new int[8][];
int linesCount = 0;
while (sc.hasNextLine()) {
int[] line = new int[8];
int count = 0;
while (sc.hasNextInt()) {
if (count >= line.length) {
line = Arrays.copyOf(line, line.length * 2);
}
line[count++] = sc.nextInt();
}
sc.nextLine();
line = Arrays.copyOf(line, count);
if (linesCount >= lines.length) {
lines = Arrays.copyOf(lines, lines.length * 2);
}
lines[linesCount++] = line;
}
PrintWriter out = getPrintWriter(linesCount, lines);
out.flush();
}
private static PrintWriter getPrintWriter(int linesCount, int[][] lines) {
PrintWriter out = new PrintWriter(System.out);
for (int i = 0; i < linesCount; i++) {
int[] line = lines[i];
for (int j = 0; j < line.length; j++) {
if (j > 0) out.print(" ");
int maxRow = lines[i][j];
for (int k = i + 1; k < linesCount; k++) {
if (lines[k].length > j && lines[k][j] > maxRow) {
maxRow = lines[k][j];
}
}
out.print(maxRow);
}
out.println();
}
return out;
}
}

View File

@@ -0,0 +1,56 @@
package reverse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class ReverseRotate {
public static void main(String[] args) throws IOException {
FastScanner sc = new FastScanner();
int[][] lines = new int[8][];
int linesCount = 0;
int maxCols = 0;
while (sc.hasNextLine()) {
int[] line = new int[8];
int count = 0;
while (sc.hasNextInt()) {
if (count >= line.length) {
line = Arrays.copyOf(line, line.length * 2);
}
line[count++] = sc.nextInt();
}
sc.nextLine();
line = Arrays.copyOf(line, count);
if (linesCount >= lines.length) {
lines = Arrays.copyOf(lines, lines.length * 2);
}
lines[linesCount++] = line;
if (count > maxCols) {
maxCols = count;
}
}
PrintWriter out = new PrintWriter(System.out);
for (int j = 0; j < maxCols; j++) {
for (int i = linesCount - 1; i >= 0; i--) {
if (lines[i].length > j) {
out.print(lines[i][j] + " ");
}
}
out.println();
}
out.flush();
}
}

View File

@@ -0,0 +1,69 @@
package reverse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class ReverseSum {
public static void main(String[] args) throws IOException {
FastScanner sc = new FastScanner();
int[][] lines = new int[8][];
int linesCount = 0;
while (sc.hasNextLine()) {
int[] line = new int[8];
int count = 0;
while (sc.hasNextInt()) {
if (count >= line.length) {
line = Arrays.copyOf(line, line.length * 2);
}
line[count++] = sc.nextInt();
}
sc.nextLine();
line = Arrays.copyOf(line, count);
if (linesCount >= lines.length) {
lines = Arrays.copyOf(lines, lines.length * 2);
}
lines[linesCount++] = line;
}
printResult(linesCount, lines);
}
private static void printResult(int linesCount, int[][] lines) {
PrintWriter out = new PrintWriter(System.out);
for (int i = 0; i < linesCount; i++) {
int[] line = lines[i];
for (int j = 0; j < line.length; j++) {
if (j > 0) out.print(" ");
int sum = 0;
for (int m = 0; m < lines[i].length; m++) {
sum += lines[i][m];
}
for (int k = 0; k < linesCount; k++) {
if (k != i && lines[k].length > j) {
sum += lines[k][j];
}
}
out.print(sum);
}
out.println();
}
out.flush();
}
}

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;

28
java/sum/Sum.java Normal file
View File

@@ -0,0 +1,28 @@
package sum;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class Sum {
public static void main(String[] args) {
int res = 0;
for (String arg : args) {
StringBuilder builder = new StringBuilder();
for (char c : arg.toCharArray()) {
if (!Character.isWhitespace(c)) {
builder.append(c);
} else {
res += (!builder.toString().isEmpty())
? Integer.parseInt(builder.toString())
: 0;
builder = new StringBuilder();
}
}
res += (!builder.toString().isEmpty())
? Integer.parseInt(builder.toString())
: 0;
}
System.out.println(res);
}
}

View File

@@ -0,0 +1,63 @@
package sum;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class SumBigDecimalHex {
public static void main(String[] args) {
BigDecimal res = new BigDecimal("0");
for (String arg : args) {
StringBuilder builder = new StringBuilder();
int idxOfS = -1;
for (char c : arg.toCharArray()) {
if (!Character.isWhitespace(c)) {
if (c == 's' || c == 'S') {
idxOfS = builder.length();
}
builder.append(c);
} else {
res = res.add(compute(builder.toString(), idxOfS));
idxOfS = -1;
builder = new StringBuilder();
}
}
res = res.add(compute(builder.toString(), idxOfS));
}
System.out.println(res);
}
static BigDecimal compute(String num, int idxOfS) {
BigDecimal res = BigDecimal.ZERO;
if (num == null || num.isEmpty()) {
return res;
} else if (num.startsWith("0x") || num.startsWith("0X")) {
num = num.toLowerCase();
num = num.substring(2);
if (idxOfS != -1) {
idxOfS -= 2;
String mantissaHex = num.substring(0, idxOfS);
String exponentHex = num.substring(idxOfS + 1);
BigInteger mantissa = new BigInteger(mantissaHex, 16);
BigInteger exponent = new BigInteger(exponentHex, 16);
res = res.add(
new BigDecimal(mantissa).scaleByPowerOfTen(
exponent.negate().intValueExact()
)
);
} else {
res = res.add(new BigDecimal(new BigInteger(num, 16)));
}
} else {
res = new BigDecimal(num);
}
return res;
}
}

View File

@@ -0,0 +1,45 @@
package sum;
import java.math.BigInteger;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class SumBigIntegerOctal {
public static void main(String[] args) {
BigInteger res = new BigInteger("0");
for (String arg : args) {
StringBuilder builder = new StringBuilder();
for (char c : arg.toCharArray()) {
if (!Character.isWhitespace(c)) {
builder.append(c);
} else {
res = res.add(compute(builder.toString()));
builder = new StringBuilder();
}
}
res = res.add(compute(builder.toString()));
}
System.out.println(res);
}
static BigInteger compute(String num) {
BigInteger res = new BigInteger("0");
int numLength = num.length();
if (num.isEmpty()) {
res = res.add(BigInteger.ZERO);
} else if (
num.charAt(numLength - 1) == 'o' || num.charAt(numLength - 1) == 'O'
) {
res = res.add(
new BigInteger(num.substring(0, num.length() - 1), 8)
);
} else {
res = res.add(new BigInteger(num));
}
return res;
}
}

28
java/sum/SumDouble.java Normal file
View File

@@ -0,0 +1,28 @@
package sum;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class SumDouble {
public static void main(String[] args) {
double res = 0.0;
for (String arg : args) {
StringBuilder builder = new StringBuilder();
for (char c : arg.toCharArray()) {
if (!Character.isWhitespace(c)) {
builder.append(c);
} else {
res += (!builder.toString().isEmpty())
? Double.parseDouble(builder.toString())
: 0;
builder = new StringBuilder();
}
}
res += (!builder.toString().isEmpty())
? Double.parseDouble(builder.toString())
: 0;
}
System.out.println(res);
}
}

View File

@@ -0,0 +1,45 @@
package sum;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class SumDoubleHex {
public static void main(String[] args) {
double res = 0;
for (String arg : args) {
StringBuilder builder = new StringBuilder();
boolean containsDot = false;
for (char c : arg.toCharArray()) {
if (!Character.isWhitespace(c)) {
builder.append(c);
if (c == '.') {
containsDot = true;
}
} else {
res += compute(builder.toString(), containsDot);
containsDot = false;
builder = new StringBuilder();
}
}
res += compute(builder.toString(), containsDot);
}
System.out.println(res);
}
static double compute(String num, boolean containsDot) {
double res = 0;
if (num.isEmpty()) {
res += 0;
} else if (
num.charAt(0) == '0' &&
(num.charAt(1) == 'x' || num.charAt(1) == 'X')
) {
res += (containsDot) ? Double.parseDouble(num) : Long.decode(num);
} else {
res += Double.parseDouble(num);
}
return res;
}
}

42
java/sum/SumHex.java Normal file
View File

@@ -0,0 +1,42 @@
package sum;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class SumHex {
public static void main(String[] args) {
int res = 0;
for (String arg : args) {
StringBuilder builder = new StringBuilder();
for (char c : arg.toCharArray()) {
if (!Character.isWhitespace(c)) {
builder.append(c);
} else {
res += compute(builder.toString());
builder = new StringBuilder();
}
}
res += compute(builder.toString());
}
System.out.println(res);
}
static int compute(String num) {
int res = 0;
if (num.isEmpty()) {
res += 0;
} else if (
num.length() >= 2 &&
num.charAt(0) == '0' &&
(num.charAt(1) == 'x' || num.charAt(1) == 'X')
) {
res += Long.decode(num);
} else {
res += Integer.parseInt(num);
}
return res;
}
}

View File

@@ -0,0 +1,45 @@
package sum;
import java.math.BigInteger;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class SumLongOctal {
public static void main(String[] args) {
long res = 0;
for (String arg : args) {
StringBuilder builder = new StringBuilder();
for (char c : arg.toCharArray()) {
if (!Character.isWhitespace(c)) {
builder.append(c);
} else {
res += compute(builder.toString());
builder = new StringBuilder();
}
}
res += compute(builder.toString());
}
System.out.println(res);
}
static long compute(String num) {
if (num.isEmpty()) {
return 0L;
}
int numLength = num.length();
if (
num.charAt(numLength - 1) == 'o' || num.charAt(numLength - 1) == 'O'
) {
return new BigInteger(
num.substring(0, num.length() - 1),
8
).longValue();
} else {
return Long.parseLong(num);
}
}
}

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,15 @@
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class WordInfo {
String word;
int count;
int firstIndex;
WordInfo(String word, int count, int firstIndex) {
this.word = word;
this.count = count;
this.firstIndex = firstIndex;
}
}

View File

@@ -0,0 +1,68 @@
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class WordStat {
public static void main(String[] args) {
if (args.length != 2) {
System.err.println("incorrect input!");
System.err.println("usage: java WordStat <inputFile> <outputFile>");
}
String inputFileName = args[0];
String outputFileName = args[1];
try {
BufferedReader r = new BufferedReader(
new FileReader(inputFileName)
);
LinkedHashMap<String, Integer> wordCount = new LinkedHashMap<>();
StringBuilder sb = new StringBuilder();
int data = r.read();
while (data != -1) {
char c = (char) data;
if (
Character.getType(c) == Character.DASH_PUNCTUATION ||
Character.isLetter(c) ||
c == '\''
) {
sb.append(c);
} else {
if (!sb.isEmpty()) {
String word = sb.toString().toLowerCase();
wordCount.put(
word,
wordCount.getOrDefault(word, 0) + 1
);
sb.setLength(0);
}
}
data = r.read();
}
r.close();
PrintWriter writer = new PrintWriter(
outputFileName,
StandardCharsets.UTF_8
);
for (Map.Entry<String, Integer> entry : wordCount.entrySet()) {
String key = entry.getKey();
int value = entry.getValue();
writer.println(key + " " + value);
}
writer.close();
} catch (Exception ex) {
System.err.println("An error occured: " + ex.getMessage());
}
}
}

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,83 @@
import java.io.*;
import java.util.*;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class WordStatLength {
public static void main(String[] args) {
if (args.length != 2) {
System.err.println("incorrect input!");
System.err.println(
"usage: java WordStatLength <inputFile> <outputFile>"
);
}
String inputFileName = args[0];
String outputFileName = args[1];
try {
BufferedReader r = new BufferedReader(
new FileReader(inputFileName)
);
Map<String, WordInfo> wordMap = new HashMap<>();
StringBuilder sb = new StringBuilder();
int wordIndex = 0;
int data = r.read();
while (data != -1) {
char c = (char) data;
if (
Character.getType(c) == Character.DASH_PUNCTUATION ||
Character.isLetter(c) ||
c == '\''
) {
sb.append(c);
} else {
if (sb.length() > 0) {
String word = sb.toString().toLowerCase();
if (wordMap.containsKey(word)) {
wordMap.get(word).count++;
} else {
wordMap.put(word, new WordInfo(word, 1, wordIndex));
wordIndex++;
}
sb.setLength(0);
}
}
data = r.read();
}
if (sb.length() > 0) {
String word = sb.toString().toLowerCase();
if (wordMap.containsKey(word)) {
wordMap.get(word).count++;
} else {
wordMap.put(word, new WordInfo(word, 1, wordIndex));
}
}
r.close();
List<WordInfo> sortedWords = new ArrayList<>(wordMap.values());
sortedWords.sort(
Comparator.comparingInt((WordInfo w) ->
w.word.length()
).thenComparingInt(w -> w.firstIndex)
);
PrintWriter writer = new PrintWriter(outputFileName, "UTF-8");
for (WordInfo info : sortedWords) {
writer.println(info.word + " " + info.count);
}
writer.close();
} catch (Exception ex) {
System.err.println("An error occured: " + ex.getMessage());
}
}
}

View File

@@ -0,0 +1,117 @@
import java.io.*;
import java.util.*;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class WordStatLengthAffix {
public static void main(String[] args) {
if (args.length != 2) {
System.err.println("incorrect input!");
System.err.println(
"usage: java WordStatLengthAffix <inputFile> <outputFile>"
);
}
String inputFileName = args[0];
String outputFileName = args[1];
try {
BufferedReader r = new BufferedReader(
new FileReader(inputFileName)
);
Map<String, WordInfo> wordMap = new HashMap<>();
StringBuilder sb = new StringBuilder();
int wordIndex = 0;
int data = r.read();
while (data != -1) {
char c = (char) data;
if (
Character.getType(c) == Character.DASH_PUNCTUATION ||
Character.isLetter(c) ||
c == '\''
) {
sb.append(c);
} else {
if (sb.length() > 0) {
String word = sb.toString().toLowerCase();
if (word.length() != 1) {
String prefix = word.substring(
0,
word.length() / 2
);
String suffix = word.substring(
word.length() - word.length() / 2
);
if (wordMap.containsKey(prefix)) {
wordMap.get(prefix).count++;
} else {
wordMap.put(
prefix,
new WordInfo(prefix, 1, wordIndex)
);
wordIndex++;
}
if (wordMap.containsKey(suffix)) {
wordMap.get(suffix).count++;
} else {
wordMap.put(
suffix,
new WordInfo(suffix, 1, wordIndex)
);
wordIndex++;
}
}
sb.setLength(0);
}
}
data = r.read();
}
if (sb.length() > 0) {
String word = sb.toString().toLowerCase();
if (word.length() != 1) {
String prefix = word.substring(0, word.length() / 2);
String suffix = word.substring(
word.length() - word.length() / 2
);
if (wordMap.containsKey(prefix)) {
wordMap.get(prefix).count++;
} else {
wordMap.put(prefix, new WordInfo(prefix, 1, wordIndex));
wordIndex++;
}
if (wordMap.containsKey(suffix)) {
wordMap.get(suffix).count++;
} else {
wordMap.put(suffix, new WordInfo(suffix, 1, wordIndex));
wordIndex++;
}
}
}
r.close();
List<WordInfo> sortedWords = new ArrayList<>(wordMap.values());
sortedWords.sort(
Comparator.comparingInt((WordInfo w) ->
w.word.length()
).thenComparingInt(w -> w.firstIndex)
);
PrintWriter writer = new PrintWriter(outputFileName, "UTF-8");
for (WordInfo info : sortedWords) {
writer.println(info.word + " " + info.count);
}
writer.close();
} catch (Exception ex) {
System.err.println("An error occured: " + ex.getMessage());
}
}
}

View File

@@ -0,0 +1,93 @@
import java.io.*;
import java.util.*;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class WordStatLengthMiddle {
public static void main(String[] args) {
if (args.length != 2) {
System.err.println("incorrect input!");
System.err.println(
"usage: java WordStatLengthMiddle <inputFile> <outputFile>"
);
}
String inputFileName = args[0];
String outputFileName = args[1];
try {
BufferedReader r = new BufferedReader(
new FileReader(inputFileName)
);
Map<String, WordInfo> wordMap = new HashMap<>();
StringBuilder sb = new StringBuilder();
int wordIndex = 0;
int data = r.read();
while (data != -1) {
char c = (char) data;
if (
Character.getType(c) == Character.DASH_PUNCTUATION ||
Character.isLetter(c) ||
c == '\''
) {
sb.append(c);
} else {
if (sb.length() > 0) {
String word = sb.toString().toLowerCase();
if (word.length() >= 7) {
word = word.substring(3, word.length() - 3);
if (wordMap.containsKey(word)) {
wordMap.get(word).count++;
} else {
wordMap.put(
word,
new WordInfo(word, 1, wordIndex)
);
wordIndex++;
}
}
sb.setLength(0);
}
}
data = r.read();
}
if (sb.length() > 0) {
String word = sb.toString().toLowerCase();
if (word.length() >= 7) {
word = word.substring(3, word.length() - 3);
if (wordMap.containsKey(word)) {
wordMap.get(word).count++;
} else {
wordMap.put(word, new WordInfo(word, 1, wordIndex));
wordIndex++;
}
}
}
r.close();
List<WordInfo> sortedWords = new ArrayList<>(wordMap.values());
sortedWords.sort(
Comparator.comparingInt((WordInfo w) ->
w.word.length()
).thenComparingInt(w -> w.firstIndex)
);
PrintWriter writer = new PrintWriter(outputFileName, "UTF-8");
for (WordInfo info : sortedWords) {
writer.println(info.word + " " + info.count);
}
writer.close();
} catch (Exception ex) {
System.err.println("An error occured: " + ex.getMessage());
}
}
}

View File

@@ -0,0 +1,96 @@
import java.io.*;
import java.util.*;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class WordStatLengthPrefix {
public static void main(String[] args) {
if (args.length != 2) {
System.err.println("incorrect input!");
System.err.println(
"usage: java WordStatLengthPrefix <inputFile> <outputFile>"
);
}
String inputFileName = args[0];
String outputFileName = args[1];
try {
BufferedReader r = new BufferedReader(
new FileReader(inputFileName)
);
Map<String, WordInfo> wordMap = new HashMap<>();
StringBuilder sb = new StringBuilder();
int wordIndex = 0;
int data = r.read();
while (data != -1) {
char c = (char) data;
if (
Character.getType(c) == Character.DASH_PUNCTUATION ||
Character.isLetter(c) ||
c == '\''
) {
sb.append(c);
} else {
if (sb.length() > 0) {
String word = sb.toString().toLowerCase();
if (word.length() != 1) {
String prefix = word.substring(
0,
word.length() / 2
);
if (wordMap.containsKey(prefix)) {
wordMap.get(prefix).count++;
} else {
wordMap.put(
prefix,
new WordInfo(prefix, 1, wordIndex)
);
wordIndex++;
}
}
sb.setLength(0);
}
}
data = r.read();
}
if (sb.length() > 0) {
String word = sb.toString().toLowerCase();
if (word.length() != 1) {
String prefix = word.substring(0, word.length() / 2);
if (wordMap.containsKey(prefix)) {
wordMap.get(prefix).count++;
} else {
wordMap.put(prefix, new WordInfo(prefix, 1, wordIndex));
wordIndex++;
}
}
}
r.close();
List<WordInfo> sortedWords = new ArrayList<>(wordMap.values());
sortedWords.sort(
Comparator.comparingInt((WordInfo w) ->
w.word.length()
).thenComparingInt(w -> w.firstIndex)
);
PrintWriter writer = new PrintWriter(outputFileName, "UTF-8");
for (WordInfo info : sortedWords) {
writer.println(info.word + " " + info.count);
}
writer.close();
} catch (Exception ex) {
System.err.println("An error occured: " + ex.getMessage());
}
}
}

View File

@@ -0,0 +1,95 @@
import java.io.*;
import java.util.*;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class WordStatLengthSuffix {
public static void main(String[] args) {
if (args.length != 2) {
System.err.println("incorrect input!");
System.err.println(
"usage: java WordStatLengthSuffix <inputFile> <outputFile>"
);
}
String inputFileName = args[0];
String outputFileName = args[1];
try {
BufferedReader r = new BufferedReader(
new FileReader(inputFileName)
);
Map<String, WordInfo> wordMap = new HashMap<>();
StringBuilder sb = new StringBuilder();
int wordIndex = 0;
int data = r.read();
while (data != -1) {
char c = (char) data;
if (
Character.getType(c) == Character.DASH_PUNCTUATION ||
Character.isLetter(c) ||
c == '\''
) {
sb.append(c);
} else {
if (sb.length() > 0) {
String word = sb.toString().toLowerCase();
if (word.length() != 1) {
word = word.substring(
word.length() - word.length() / 2
);
if (wordMap.containsKey(word)) {
wordMap.get(word).count++;
} else {
wordMap.put(
word,
new WordInfo(word, 1, wordIndex)
);
wordIndex++;
}
}
sb.setLength(0);
}
}
data = r.read();
}
if (sb.length() > 0) {
String word = sb.toString().toLowerCase();
if (word.length() != 1) {
word = word.substring(word.length() - word.length() / 2);
if (wordMap.containsKey(word)) {
wordMap.get(word).count++;
} else {
wordMap.put(word, new WordInfo(word, 1, wordIndex));
wordIndex++;
}
}
}
r.close();
List<WordInfo> sortedWords = new ArrayList<>(wordMap.values());
sortedWords.sort(
Comparator.comparingInt((WordInfo w) ->
w.word.length()
).thenComparingInt(w -> w.firstIndex)
);
PrintWriter writer = new PrintWriter(outputFileName, "UTF-8");
for (WordInfo info : sortedWords) {
writer.println(info.word + " " + info.count);
}
writer.close();
} catch (Exception ex) {
System.err.println("An error occured: " + ex.getMessage());
}
}
}

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;

44
java/wspp/IntList.java Normal file
View File

@@ -0,0 +1,44 @@
package wspp;
import java.util.Arrays;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class IntList {
protected int[] list = new int[8];
protected int idx = 0;
public IntList() {}
public void put(int val) {
if (idx >= list.length) {
list = Arrays.copyOf(list, list.length * 2);
}
list[idx++] = val;
}
public int getLength() {
return idx;
}
public int get(int index) {
return list[index];
}
public String toString() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < idx; i++) {
if (i == idx - 1) {
sb.append(String.valueOf(list[i]) + "\n");
} else {
sb.append(String.valueOf(list[i]) + " ");
}
}
return sb.toString();
}
}

25
java/wspp/WordInfo.java Normal file
View File

@@ -0,0 +1,25 @@
package wspp;
import java.util.*;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class WordInfo {
final String word;
int firstOccurrence;
IntList occurrences = new IntList();
Map<Integer, IntList> lineOccurrences = new HashMap<>();
public WordInfo(String word) {
this.word = word;
}
public WordInfo(String word, int firstOccurrence) {
this.word = word;
this.firstOccurrence = firstOccurrence;
}
}

80
java/wspp/Wspp.java Normal file
View File

@@ -0,0 +1,80 @@
package wspp;
import java.io.*;
import java.util.*;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class Wspp {
public static void main(String[] args) {
if (args.length != 2) {
System.err.println(
"usage: java Wspp <inputFilePath> <outputFilePath>"
);
}
final String inputFileName = args[0];
final String outputFileName = args[1];
Map<String, WordInfo> words = new LinkedHashMap<>();
try (
BufferedReader br = new BufferedReader(
new FileReader(inputFileName)
);
FileWriter fw = new FileWriter(outputFileName)
) {
String line;
int wordPos = 1;
while ((line = br.readLine()) != null) {
line = line.toLowerCase();
StringBuilder word = new StringBuilder();
for (char c : line.toCharArray()) {
if (
Character.isLetter(c) ||
c == '\'' ||
Character.getType(c) == Character.DASH_PUNCTUATION
) {
word.append(c);
} else {
if (!word.isEmpty()) {
if (words.containsKey(word.toString())) {
words
.get(word.toString())
.occurrences.put(wordPos++);
} else {
WordInfo info = new WordInfo(word.toString());
info.occurrences.put(wordPos++);
words.put(word.toString(), info);
}
}
word = new StringBuilder();
}
}
if (!word.isEmpty()) {
if (words.containsKey(word.toString())) {
words.get(word.toString()).occurrences.put(wordPos++);
} else {
WordInfo info = new WordInfo(word.toString());
info.occurrences.put(wordPos++);
words.put(word.toString(), info);
}
}
}
for (String word : words.keySet()) {
WordInfo info = words.get(word);
int count = info.occurrences.getLength();
String occurencies = info.occurrences.toString();
fw.write(word + " " + count + " " + occurencies);
}
} catch (IOException e) {
System.out.println("Error reading file.");
}
}
}

127
java/wspp/WsppLast.java Normal file
View File

@@ -0,0 +1,127 @@
package wspp;
import java.io.*;
import java.util.*;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class WsppLast {
public static void main(String[] args) {
if (args.length != 2) {
System.err.println(
"usage: java WsppPosition <inputFilePath> <outputFilePath>"
);
}
final String inputFileName = args[0];
final String outputFileName = args[1];
Map<String, WordInfo> words = new LinkedHashMap<>();
try (
BufferedReader br = new BufferedReader(
new FileReader(inputFileName)
);
FileWriter fw = new FileWriter(outputFileName)
) {
String line;
int wordPos = 1;
int lineNumber = 1;
while ((line = br.readLine()) != null) {
line = line.toLowerCase();
StringBuilder word = new StringBuilder();
for (char c : line.toCharArray()) {
if (
Character.isLetter(c) ||
c == '\'' ||
Character.getType(c) == Character.DASH_PUNCTUATION ||
Character.isDigit(c) ||
c == '$' ||
c == '_'
) {
word.append(c);
} else {
if (!word.isEmpty()) {
if (words.containsKey(word.toString())) {
var lO = words.get(
word.toString()
).lineOccurrences;
if (lO.containsKey(lineNumber)) {
lO.get(lineNumber).put(wordPos++);
} else {
var intList = new IntList();
intList.put(wordPos++);
lO.put(lineNumber, intList);
}
} else {
WordInfo info = new WordInfo(
word.toString(),
wordPos
);
var intList = new IntList();
intList.put(wordPos++);
info.lineOccurrences.put(lineNumber, intList);
words.put(word.toString(), info);
}
}
word = new StringBuilder();
}
}
if (!word.isEmpty()) {
if (words.containsKey(word.toString())) {
var lO = words.get(word.toString()).lineOccurrences;
if (lO.containsKey(lineNumber)) {
lO.get(lineNumber).put(wordPos++);
} else {
var intList = new IntList();
intList.put(wordPos++);
lO.put(lineNumber, intList);
}
} else {
WordInfo info = new WordInfo(word.toString(), wordPos);
var intList = new IntList();
intList.put(wordPos++);
info.lineOccurrences.put(lineNumber, intList);
words.put(word.toString(), info);
}
}
lineNumber++;
}
List<WordInfo> sortedWords = new ArrayList<>(words.values());
sortedWords.sort(
Comparator.comparingInt((WordInfo w) ->
w.word.length()
).thenComparingInt(w -> w.firstOccurrence)
);
for (WordInfo info : sortedWords) {
int totalNumberOfOccurrences = 0;
var lO = info.lineOccurrences;
String word = info.word;
for (int key : lO.keySet()) {
totalNumberOfOccurrences += lO.get(key).getLength();
}
fw.write(word + " " + totalNumberOfOccurrences);
for (int key : lO.keySet()) {
var occurrences = lO.get(key);
fw.write(
" " + occurrences.get(occurrences.getLength() - 1)
);
}
fw.write("\n");
}
} catch (IOException e) {
System.out.println("Error reading file.");
}
}
}

127
java/wspp/WsppMiddle.java Normal file
View File

@@ -0,0 +1,127 @@
package wspp;
import java.io.*;
import java.util.*;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class WsppMiddle {
public static void main(String[] args) {
if (args.length != 2) {
System.err.println(
"usage: java WsppMiddle <inputFilePath> <outputFilePath>"
);
}
final String inputFileName = args[0];
final String outputFileName = args[1];
Map<String, WordInfo> words = new LinkedHashMap<>();
try (
BufferedReader br = new BufferedReader(
new FileReader(inputFileName)
);
FileWriter fw = new FileWriter(outputFileName)
) {
String line;
int wordPos = 1;
int lineNumber = 1;
while ((line = br.readLine()) != null) {
line = line.toLowerCase();
StringBuilder word = new StringBuilder();
for (char c : line.toCharArray()) {
if (
Character.isLetter(c) ||
c == '\'' ||
Character.getType(c) == Character.DASH_PUNCTUATION ||
Character.isDigit(c) ||
c == '$' ||
c == '_'
) {
word.append(c);
} else {
if (!word.isEmpty()) {
if (words.containsKey(word.toString())) {
var lO = words.get(
word.toString()
).lineOccurrences;
if (lO.containsKey(lineNumber)) {
lO.get(lineNumber).put(wordPos++);
} else {
var intList = new IntList();
intList.put(wordPos++);
lO.put(lineNumber, intList);
}
} else {
WordInfo info = new WordInfo(
word.toString(),
wordPos
);
var intList = new IntList();
intList.put(wordPos++);
info.lineOccurrences.put(lineNumber, intList);
words.put(word.toString(), info);
}
}
word = new StringBuilder();
}
}
if (!word.isEmpty()) {
if (words.containsKey(word.toString())) {
var lO = words.get(word.toString()).lineOccurrences;
if (lO.containsKey(lineNumber)) {
lO.get(lineNumber).put(wordPos++);
} else {
var intList = new IntList();
intList.put(wordPos++);
lO.put(lineNumber, intList);
}
} else {
WordInfo info = new WordInfo(word.toString(), wordPos);
var intList = new IntList();
intList.put(wordPos++);
info.lineOccurrences.put(lineNumber, intList);
words.put(word.toString(), info);
}
}
lineNumber++;
}
List<WordInfo> sortedWords = new ArrayList<>(words.values());
sortedWords.sort(
Comparator.comparingInt((WordInfo w) ->
w.word.length()
).thenComparingInt(w -> w.firstOccurrence)
);
for (WordInfo info : sortedWords) {
int totalNumberOfOccurrences = 0;
var lO = info.lineOccurrences;
String word = info.word;
for (int key : lO.keySet()) {
totalNumberOfOccurrences += lO.get(key).getLength();
}
fw.write(word + " " + totalNumberOfOccurrences);
for (int key : lO.keySet()) {
var occurrences = lO.get(key);
fw.write(
" " + occurrences.get(occurrences.getLength() / 2)
);
}
fw.write("\n");
}
} catch (IOException e) {
System.out.println("Error reading file.");
}
}
}

119
java/wspp/WsppPos.java Normal file
View File

@@ -0,0 +1,119 @@
package wspp;
import java.io.*;
import java.util.*;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class WsppPos {
public static void main(String[] args) {
if (args.length != 2) {
System.err.println(
"usage: java WsppPos <inputFilePath> <outputFilePath>"
);
}
final String inputFileName = args[0];
final String outputFileName = args[1];
Map<String, WordInfo> words = new LinkedHashMap<>();
try (
BufferedReader br = new BufferedReader(
new FileReader(inputFileName)
);
FileWriter fw = new FileWriter(outputFileName)
) {
String line;
int wordPos = 1;
int lineNumber = 1;
while ((line = br.readLine()) != null) {
line = line.toLowerCase();
StringBuilder word = new StringBuilder();
for (char c : line.toCharArray()) {
if (
Character.isLetter(c) ||
c == '\'' ||
Character.getType(c) == Character.DASH_PUNCTUATION ||
Character.isDigit(c) ||
c == '$' ||
c == '_'
) {
word.append(c);
} else {
if (!word.isEmpty()) {
if (words.containsKey(word.toString())) {
var lO = words.get(
word.toString()
).lineOccurrences;
if (lO.containsKey(lineNumber)) {
lO.get(lineNumber).put(wordPos++);
} else {
var intList = new IntList();
intList.put(wordPos++);
lO.put(lineNumber, intList);
}
} else {
WordInfo info = new WordInfo(word.toString());
var intList = new IntList();
intList.put(wordPos++);
info.lineOccurrences.put(lineNumber, intList);
words.put(word.toString(), info);
}
}
word = new StringBuilder();
}
}
if (!word.isEmpty()) {
if (words.containsKey(word.toString())) {
var lO = words.get(word.toString()).lineOccurrences;
if (lO.containsKey(lineNumber)) {
lO.get(lineNumber).put(wordPos++);
} else {
var intList = new IntList();
intList.put(wordPos++);
lO.put(lineNumber, intList);
}
} else {
WordInfo info = new WordInfo(word.toString());
var intList = new IntList();
intList.put(wordPos++);
info.lineOccurrences.put(lineNumber, intList);
words.put(word.toString(), info);
}
}
lineNumber++;
}
for (String word : words.keySet()) {
int totalNumberOfOccurrences = 0;
WordInfo info = words.get(word);
var lO = info.lineOccurrences;
for (int key : lO.keySet()) {
totalNumberOfOccurrences += lO.get(key).getLength();
}
fw.write(word + " " + totalNumberOfOccurrences);
for (int key : lO.keySet()) {
var occurrences = lO.get(key);
for (int i = 0; i < occurrences.getLength(); i++) {
fw.write(
" " + key + ":" + (wordPos - occurrences.get(i))
);
}
}
fw.write("\n");
}
} catch (IOException e) {
System.out.println("Error reading file.");
}
}
}

129
java/wspp/WsppPosition.java Normal file
View File

@@ -0,0 +1,129 @@
package wspp;
import java.io.*;
import java.util.*;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public class WsppPosition {
public static void main(String[] args) {
if (args.length != 2) {
System.err.println(
"usage: java WsppPosition <inputFilePath> <outputFilePath>"
);
}
final String inputFileName = args[0];
final String outputFileName = args[1];
Map<String, WordInfo> words = new LinkedHashMap<>();
try (
BufferedReader br = new BufferedReader(
new FileReader(inputFileName)
);
FileWriter fw = new FileWriter(outputFileName)
) {
String line;
int wordPos = 1;
int lineNumber = 1;
while ((line = br.readLine()) != null) {
line = line.toLowerCase();
StringBuilder word = new StringBuilder();
for (char c : line.toCharArray()) {
if (
Character.isLetter(c) ||
c == '\'' ||
Character.getType(c) == Character.DASH_PUNCTUATION ||
Character.isDigit(c) ||
c == '$' ||
c == '_'
) {
word.append(c);
} else {
if (!word.isEmpty()) {
if (words.containsKey(word.toString())) {
var lO = words.get(
word.toString()
).lineOccurrences;
if (lO.containsKey(lineNumber)) {
lO.get(lineNumber).put(wordPos++);
} else {
var intList = new IntList();
intList.put(wordPos++);
lO.put(lineNumber, intList);
}
} else {
WordInfo info = new WordInfo(
word.toString(),
wordPos
);
var intList = new IntList();
intList.put(wordPos++);
info.lineOccurrences.put(lineNumber, intList);
words.put(word.toString(), info);
}
}
word = new StringBuilder();
}
}
if (!word.isEmpty()) {
if (words.containsKey(word.toString())) {
var lO = words.get(word.toString()).lineOccurrences;
if (lO.containsKey(lineNumber)) {
lO.get(lineNumber).put(wordPos++);
} else {
var intList = new IntList();
intList.put(wordPos++);
lO.put(lineNumber, intList);
}
} else {
WordInfo info = new WordInfo(word.toString(), wordPos);
var intList = new IntList();
intList.put(wordPos++);
info.lineOccurrences.put(lineNumber, intList);
words.put(word.toString(), info);
}
}
lineNumber++;
}
List<WordInfo> sortedWords = new ArrayList<>(words.values());
sortedWords.sort(
Comparator.comparingInt((WordInfo w) ->
w.word.length()
).thenComparingInt(w -> w.firstOccurrence)
);
for (WordInfo info : sortedWords) {
int totalNumberOfOccurrences = 0;
var lO = info.lineOccurrences;
String word = info.word;
for (int key : lO.keySet()) {
totalNumberOfOccurrences += lO.get(key).getLength();
}
fw.write(word + " " + totalNumberOfOccurrences);
for (int key : lO.keySet()) {
var occurrences = lO.get(key);
for (int i = 0; i < occurrences.getLength(); i++) {
fw.write(
" " + key + ":" + (wordPos - occurrences.get(i))
);
}
}
fw.write("\n");
}
} catch (IOException e) {
System.out.println("Error reading file.");
}
}
}

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;