commit 2f05f238e98621bb4cd3f6abb541039d4b07f690 Author: me Date: Tue Feb 17 09:32:08 2026 +0300 update diff --git a/.gitea/workflows/expression.yml b/.gitea/workflows/expression.yml new file mode 100644 index 0000000..0058b1d --- /dev/null +++ b/.gitea/workflows/expression.yml @@ -0,0 +1,22 @@ +name: Expression Tests + +on: + # push: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Compile Java + run: | + mkdir -p out + javac -d out $(find java -name "*.java") + + - name: Run Expression tests + run: | + java -ea -cp out expression.ExpressionTest Base diff --git a/.gitea/workflows/fast-reverse.yml b/.gitea/workflows/fast-reverse.yml new file mode 100644 index 0000000..5a98171 --- /dev/null +++ b/.gitea/workflows/fast-reverse.yml @@ -0,0 +1,22 @@ +name: Fast Reverse Tests + +on: + # push: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Compile Java + run: | + mkdir -p out + javac -d out $(find java -name "*.java") + + - name: Run Fast Reverse tests + run: | + java -ea -cp out reverse.FastReverseTest Base 3233 diff --git a/.gitea/workflows/markup.yml b/.gitea/workflows/markup.yml new file mode 100644 index 0000000..ff3bc70 --- /dev/null +++ b/.gitea/workflows/markup.yml @@ -0,0 +1,26 @@ +name: Markup Tests + +on: + push: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Compile Java + run: | + mkdir -p out + javac -d out $(find java -name "*.java") + + - name: Run Markup tests + run: | + java -ea -cp out markup.MarkupTest Base 3233 3435 3637 3839 4142 4749 + + - name: Run Markup List tests + run: | + java -ea -cp out markup.MarkupListTest 3637 3839 4142 4749 diff --git a/.gitea/workflows/md2html.yml b/.gitea/workflows/md2html.yml new file mode 100644 index 0000000..8fb8185 --- /dev/null +++ b/.gitea/workflows/md2html.yml @@ -0,0 +1,22 @@ +name: Markdown to Html Tests + +on: + push: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Compile Java + run: | + mkdir -p out + javac -d out $(find java -name "*.java") + + - name: Run Markdown to Html tests + run: | + java -ea -cp out md2html.Md2HtmlTest Base diff --git a/.gitea/workflows/reverse.yml b/.gitea/workflows/reverse.yml new file mode 100644 index 0000000..e892035 --- /dev/null +++ b/.gitea/workflows/reverse.yml @@ -0,0 +1,22 @@ +name: Reverse Tests + +on: + # push: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Compile Java + run: | + mkdir -p out + javac -d out $(find java -name "*.java") + + - name: Run Reverse tests + run: | + java -ea -cp out reverse.ReverseTest Base 3233 3435 3637 3839 4142 4749 diff --git a/.gitea/workflows/sum.yml b/.gitea/workflows/sum.yml new file mode 100644 index 0000000..681bd59 --- /dev/null +++ b/.gitea/workflows/sum.yml @@ -0,0 +1,22 @@ +name: Sum Tests + +on: + # push: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Compile Java + run: | + mkdir -p out + javac -d out $(find java -name "*.java") + + - name: Run Sum tests + run: | + java -ea -cp out sum.SumTest Base 3233 3435 3637 3839 4142 4749 diff --git a/.gitea/workflows/word-stat.yml b/.gitea/workflows/word-stat.yml new file mode 100644 index 0000000..89acd81 --- /dev/null +++ b/.gitea/workflows/word-stat.yml @@ -0,0 +1,22 @@ +name: Word Stat Tests + +on: + # push: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Compile Java + run: | + mkdir -p out + javac -d out $(find java -name "*.java") + + - name: Run Word Stat tests + run: | + java -ea -cp out wordStat.WordStatTest Base 3233 3435 3637 3839 4142 4749 diff --git a/.gitea/workflows/wspp.yml b/.gitea/workflows/wspp.yml new file mode 100644 index 0000000..3478641 --- /dev/null +++ b/.gitea/workflows/wspp.yml @@ -0,0 +1,22 @@ +name: Word Stat++ Tests + +on: + # push: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Compile Java + run: | + mkdir -p out + javac -d out $(find java -name "*.java") + + - name: Run Word Stat++ tests + run: | + java -ea -cp out wspp.WsppTest Base 3233 3435 3637 3839 4142 4749 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..743e2b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.xml +java/out/* +*.iml +*.idea* +*.class diff --git a/README.md b/README.md new file mode 100644 index 0000000..71d18cf --- /dev/null +++ b/README.md @@ -0,0 +1,554 @@ +--- +gitea: none +include_toc: true +--- + +# Тесты к курсу «Введение в программирование» + +[Условия домашних заданий](https://www.kgeorgiy.info/courses/prog-intro/homeworks.html) + + + +## Домашнее задание 9. Markdown to HTML [![Markdown to HTML Tests](https://git.fymio.us/me/prog-intro-2025/actions/workflows/md2html.yml/badge.svg)](https://git.fymio.us/me/prog-intro-2025/actions) + +Модификации + * *Base* ✅ + * [Исходный код тестов](java/md2html/Md2HtmlTester.java) + * [Откомпилированные тесты](artifacts/Md2HtmlTest.jar) + * Аргументы командной строки: модификации + * *3637* + * Добавьте поддержку + `<<вставок>>`: `вставок` и + `}}удалений{{`: `удалений` + * *3839* + * Добавьте поддержку + \`\`\``кода __без__ форматирования`\`\`\`: + `
кода __без__ форматирования
` + * *3233* + * Добавьте поддержку `%переменных%%`: `переменных` + * *3435* + * Добавьте поддержку `!!примеров!!`: `примеров` + + +## Домашнее задание 7. Разметка [![Markup Tests](https://git.fymio.us/me/prog-intro-2025/actions/workflows/markup.yml/badge.svg)](https://git.fymio.us/me/prog-intro-2025/actions) + +Модификации + * *Base* ✅ + * Исходный код тестов: + * [MarkupTester.java](java/markup/MarkupTester.java) + * [MarkupTest.java](java/markup/MarkupTest.java) + * Аргументы командной строки: модификации + * Откомпилированных тестов не существует, + так как они зависят от вашего кода + * *3637*, *3839*, *4142*, *4749* ✅ + * Дополнительно реализуйте метод `toTex`, генерирующий TeX-разметку: + * Абзацы предваряются командой `\par{}` + * Выделенный текст заключается в `\emph{` и `}` + * Сильно выделенный текст заключается в `\textbf{` и `}` + * Зачеркнутый текст заключается в `\textst{` и `}` + * Добавьте поддержку: + * Нумерованных списков (класс `OrderedList`, окружение `enumerate`): последовательность элементов + * Ненумерованных списков (класс `UnorderedList`, окружение `itemize`): последовательность элементов + * Элементов списка (класс `ListItem`, тег `\item`: последовательность абзацев и списков + * Для новых классов поддержка Markdown не требуется + * [Исходный код тестов](java/markup/MarkupListTest.java) + * *3233*, *3435* ✅ + * Дополнительно реализуйте метод `toHtml`, генерирующий HTML-разметку: + * Абзацы окружаются тегом `p` + * Выделенный текст окружается тегом `em` + * Сильно выделенный текст окружается тегом `strong` + * Зачеркнутый текст окружается тегом `s` + + +## Домашнее задание 6. Подсчет слов++ [![Word Stat++ Tests](https://git.fymio.us/me/prog-intro-2025/actions/workflows/wspp.yml/badge.svg)](https://git.fymio.us/me/prog-intro-2025/actions) + +Модификации + * *Base* ✅ + * Класс должен иметь имя `Wspp` + * Исходный код тестов: + [WsppTest.java](java/wspp/WsppTest.java), + [WsppTester.java](java/wspp/WsppTester.java) + * Откомпилированные тесты: [WsppTest.jar](artifacts/WsppTest.jar) + * Аргументы командной строки: модификации + * *3637* ✅ + * В выходном файле слова должны быть упорядочены + по возрастанию длины, а при равной длине – + по порядку первого вхождения во входной файл + * Вместо всех вхождений в файле надо указывать + только последнее вхождение в строке + * В словах могут дополнительно встречаться + цифры и символы `$` и `_` + * Класс должен иметь имя `WsppLast` + * *3839* ✅ + * В выходном файле слова должны быть упорядочены + по возрастанию длины, а при равной длине – + по порядку первого вхождения во входной файл + * Вместо всех вхождений в файле надо указывать + только среднее вхождение строке + * В словах могут дополнительно встречаться + цифры и символы `$` и `_` + * Класс должен иметь имя `WsppMiddle` + * *3435* ✅ + * В выходном файле слова должны быть упорядочены + по возрастанию длины, а при равной длине – + по порядку первого вхождения во входной файл + * Вместо номеров вхождений во всем файле надо указывать + `<номер строки>:<номер вхождения>`, + где номер вхождения считается с конца файла + * В словах могут дополнительно встречаться + цифры и символы `$` и `_` + * Класс должен иметь имя `WsppPosition` + * *3233* ✅ + * В выходном файле слова должны быть упорядочены + в порядке вхождения во входной файл + * Вместо номеров вхождений во всем файле надо указывать + `<номер строки>:<номер вхождения>`, + где номер вхождения считается с конца файла + * В словах могут дополнительно встречаться + цифры и символы `$` и `_` + * Класс должен иметь имя `WsppPos` + * *4142* ✅ + * В выходном файле слова должны быть упорядочены + по возрастанию длины, а при равной длине – + по порядку первого вхождения во входной файл + * Вместо всех вхождений в файле надо указывать + только последнее вхождение в строке + * В словах могут дополнительно встречаться + цифры и символы `$` и `_` + * Класс должен иметь имя `WsppLast` + * *4749* ✅ + * В выходном файле слова должны быть упорядочены + по возрастанию длины, а при равной длине – + по порядку первого вхождения во входной файл + * Вместо номеров вхождений во всем файле надо указывать + `<номер строки>:<номер вхождения>`, + где номер вхождения считается с конца файла + * В словах могут дополнительно встречаться + цифры и символы `$` и `_` + * Класс должен иметь имя `WsppPosition` + + + +## Домашнее задание 4. Подсчет слов [![Word Stat](https://git.fymio.us/me/prog-intro-2025/actions/workflows/word-stat.yml/badge.svg)](https://git.fymio.us/me/prog-intro-2025/actions) + + +Модификации + * *Base* ✅ + * Класс должен иметь имя `WordStat` + * Исходный код тестов: + [WordStatTest.java](java/wordStat/WordStatTest.java), + [WordStatTester.java](java/wordStat/WordStatTester.java), + [WordStatChecker.java](java/wordStat/WordStatChecker.java) + * Откомпилированные тесты: [WordStatTest.jar](artifacts/WordStatTest.jar) + * Аргументы командной строки: модификации + * *FastSort* ✅ + * Пусть _n_ – число слов во входном файле, + тогда программа должна работать за O(_n_ log _n_). + * *3637* ✅ + * Назовём _серединой слова_ подстроку, полученную удалением + первых и последних 3 символов слова. + Слова длины меньшей 7 игнорируются. + * Выходной файл должен содержать все различные + середины слов, встречающихся во входном файле, + упорядоченные по возрастанию длины (при равенстве – по первому вхождению). + * Класс должен иметь имя `WordStatLengthMiddle` + * *3839* ✅ + * Назовём _аффиксами слова_ + его префикс и суффикс длины `n / 2`, где `n` — длина слова. + Слова длины один игнорируются. + * Выходной файл должен содержать все различные + аффиксы слов, встречающихся во входном файле, + упорядоченные по возрастанию длины (при равенстве – по первому вхождению). + * Класс должен иметь имя `WordStatLengthAffix` + * *3435* ✅ + * Назовём _суффиксом слова_ подстроку, + состоящую из `n / 2` последних символов слова, где `n` — длина слова. + Слова длины один игнорируются. + * Выходной файл должен содержать все различные + суффиксы слов, встречающихся во входном файле, + упорядоченные по возрастанию длины (при равенстве – по первому вхождению). + * Класс должен иметь имя `WordStatLengthSuffix` + * *3233* ✅ + * Выходной файл должен содержать все различные + слова встречающиеся во входном файле, + упорядоченные по возрастанию длины (при равенстве – по первому вхождению). + * Класс должен иметь имя `WordStatLength` + * *4142* ✅ + * Назовём _серединой слова_ подстроку, полученную удалением + первых и последних 3 символов слова. + Слова длины меньшей 7 игнорируются. + * Выходной файл должен содержать все различные + середины слов, встречающихся во входном файле, + упорядоченные по возрастанию длины (при равенстве – по первому вхождению). + * Класс должен иметь имя `WordStatLengthMiddle` + * *4749* ✅ + * Назовём _префиксом слова_ подстроку, + состоящую из `n / 2` первых символов слова, где `n` — длина слова. + Слова длины один игнорируются. + * Выходной файл должен содержать все различные + префиксы слов, встречающихся во входном файле, + упорядоченные по возрастанию длины (при равенстве – по первому вхождению). + * Класс должен иметь имя `WordStatLengthPrefix` + +## Домашнее задание 3. Реверс [![Reverse Tests](https://git.fymio.us/me/prog-intro-2025/actions/workflows/reverse.yml/badge.svg)](https://git.fymio.us/me/prog-intro-2025/actions) + + +Модификации + * *Base* ✅ + * Исходный код тестов: + [ReverseTest.java](java/reverse/ReverseTest.java), + [ReverseTester.java](java/reverse/ReverseTester.java) + * Откомпилированные тесты: [ReverseTest.jar](artifacts/ReverseTest.jar) + * Аргументы командной строки: модификации + * *Memory* ✅ + * Программа должна сначала считывать все данные в память, + и только потом обрабатывать их. + * Пусть _M_ – объём памяти, необходимый для сохранения ввода + в двумерном массиве `int` минимального размера. + Ваша программа должна использовать не более 4_M_ + 1024 байт памяти. + * Накладные расходы на запуск вашей программы JVM не учитываются. + * *3637* ✅ + * Рассмотрим входные данные как (не полностью определенную) матрицу, + вместо каждого числа выведите максимум из чисел, + находящихся в его столбце в последующих строках, и его самого + * Класс должен иметь имя `ReverseMaxC` + * *3839* + * Рассмотрим входные данные как (не полностью определенную) матрицу, + вместо каждого числа выведите максимум из чисел + текущее число — правый нижний угол матрицы + * Класс должен иметь имя `ReverseMax` + * *3435* ✅ + * Рассмотрим входные данные как (не полностью определенную) матрицу, + выведите ее поворот по часовой стрелке, например для ввода + ``` + 1 2 3 4 + 5 6 + 7 8 9 + ``` + вывод должен быть + ``` + 7 5 1 + 8 6 2 + 9 3 + 4 + ``` + * Класс должен иметь имя `ReverseRotate` + * *3233* ✅ + * Выведите (в реверсивном порядке) только числа, + у которых сумма номеров строки и столбца четная + * Класс должен иметь имя `ReverseEven` + * *4142* ✅ + * Рассмотрим входные данные как (не полностью определенную) матрицу, + вместо каждого числа выведите среднее из чисел в его столбце и строке + * Класс должен иметь имя `ReverseAvg` + * *4749* ✅ + * Рассмотрим входные данные как (не полностью определенную) матрицу, + вместо каждого числа выведите сумму чиселв его столбце и строке + * Класс должен иметь имя `ReverseSum` + +## Домашнее задание 2. Сумма чисел [![Sum Tests](https://git.fymio.us/me/prog-intro-2025/actions/workflows/sum.yml/badge.svg)](https://git.fymio.us/me/prog-intro-2025/actions) + +Модификации + * *Base* ✅ + * Исходный код тестов: + [SumTest.java](java/sum/SumTest.java), + [SumTester.java](java/sum/SumTester.java), + [базовые классы](java/base/) + * Откомпилированные тесты: [SumTest.jar](artifacts/SumTest.jar) + * Аргументы командной строки: модификации + * *3637* ✅ + * Входные данные являются 64-битными числами в формате с плавающей точкой + * На вход подаются десятичные и шестнадцатеричные числа + * Шестнадцатеричные числа имеют префикс `0x`, + например `0xa.bp2` равно (10+11/16)·4 равно 42.75 + * Ввод регистронезависим + * Класс должен иметь имя `SumDoubleHex` + * *3839* ✅ + * Входные данные помещаются в тип [BigDecimal](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/math/BigDecimal.html) + * На вход подаются десятичные и шестнадцатеричные числа + * Шестнадцатеричные числа имеют префикс `0x`, + например `0xbsc` равно 11·10⁻¹² + (мантисса и порядок являются целыми числами) + * Ввод регистронезависим + * Класс должен иметь имя `SumBigDecimalHex` + * *3435* ✅ + * На вход подаются десятичные и шестнадцатеричные числа + * Шестнадцатеричные числа имеют префикс `0x` + * Ввод регистронезависим + * Класс должен иметь имя `SumHex` + * *3233* ✅ + * Входные данные являются 64-битными числами в формате с плавающей точкой + * Класс должен иметь имя `SumDouble` + * *4142* ✅ + * Входные данные помещаются в тип [BigInteger](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/math/BigInteger.html) + * восьмеричные числа имеют суффикс `o` + * Класс должен иметь имя `SumBigIntegerOctal` + * *4749* ✅ + * Входные данные являются 64-битными целыми числами + * восьмеричные числа имеют суффикс `o` + * Класс должен иметь имя `SumLongOctal` + diff --git a/artifacts/FastReverseTest.jar b/artifacts/FastReverseTest.jar new file mode 100644 index 0000000..f3d1384 Binary files /dev/null and b/artifacts/FastReverseTest.jar differ diff --git a/artifacts/Md2HtmlTest.jar b/artifacts/Md2HtmlTest.jar new file mode 100644 index 0000000..228be12 Binary files /dev/null and b/artifacts/Md2HtmlTest.jar differ diff --git a/artifacts/ReverseTest.jar b/artifacts/ReverseTest.jar new file mode 100644 index 0000000..86ec21b Binary files /dev/null and b/artifacts/ReverseTest.jar differ diff --git a/artifacts/SumTest.jar b/artifacts/SumTest.jar new file mode 100644 index 0000000..1a0bb8d Binary files /dev/null and b/artifacts/SumTest.jar differ diff --git a/artifacts/WordStatTest.jar b/artifacts/WordStatTest.jar new file mode 100644 index 0000000..a047faa Binary files /dev/null and b/artifacts/WordStatTest.jar differ diff --git a/artifacts/WsppTest.jar b/artifacts/WsppTest.jar new file mode 100644 index 0000000..12a8e26 Binary files /dev/null and b/artifacts/WsppTest.jar differ diff --git a/java/RunMe.java b/java/RunMe.java new file mode 100644 index 0000000..d10b0b3 --- /dev/null +++ b/java/RunMe.java @@ -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 + *
+     *    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
+     * 
+ */ + 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 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 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 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 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 VALUES = IntStream.range(0, KEYWORDS.size()) + .boxed() + .collect(Collectors.toMap(index -> KEYWORDS.get(index).toLowerCase(Locale.US), Integer::byteValue)); +} diff --git a/java/base/Asserts.java b/java/base/Asserts.java new file mode 100644 index 0000000..17ea3e6 --- /dev/null +++ b/java/base/Asserts.java @@ -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 void assertEquals(final String message, final List expected, final List 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); + } +} diff --git a/java/base/BaseChecker.java b/java/base/BaseChecker.java new file mode 100644 index 0000000..67bd57c --- /dev/null +++ b/java/base/BaseChecker.java @@ -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(); + } +} diff --git a/java/base/Either.java b/java/base/Either.java new file mode 100644 index 0000000..8a3eca8 --- /dev/null +++ b/java/base/Either.java @@ -0,0 +1,95 @@ +package base; + +import java.util.function.Function; + +/** + * @author Georgiy Korneev (kgeorgiy@kgeorgiy.info) + */ +public interface Either { + Either mapRight(final Function f); + Either flatMapRight(final Function> f); + T either(Function lf, Function rf); + + boolean isRight(); + + L getLeft(); + R getRight(); + + static Either right(final R value) { + return new Either<>() { + @Override + public Either mapRight(final Function f) { + return right(f.apply(value)); + } + + @Override + public Either flatMapRight(final Function> f) { + return f.apply(value); + } + + @Override + public T either(final Function lf, final Function 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 Either left(final L value) { + return new Either<>() { + @Override + public Either mapRight(final Function f) { + return left(value); + } + + @Override + public Either flatMapRight(final Function> f) { + return left(value); + } + + @Override + public T either(final Function lf, final Function 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); + } + }; + } +} diff --git a/java/base/ExtendedRandom.java b/java/base/ExtendedRandom.java new file mode 100644 index 0000000..ac2b059 --- /dev/null +++ b/java/base/ExtendedRandom.java @@ -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 randomItem(final T... items) { + return items[nextInt(items.length)]; + } + + public T randomItem(final List items) { + return items.get(nextInt(items.size())); + } + + public Random getRandom() { + return random; + } + + public List random(final int list, final Function generator) { + return Stream.generate(() -> generator.apply(this)).limit(list).toList(); + } + + public double nextDouble() { + return random.nextDouble(); + } + + public void shuffle(final List all) { + Collections.shuffle(all, random); + } +} diff --git a/java/base/Functional.java b/java/base/Functional.java new file mode 100644 index 0000000..ef14dd5 --- /dev/null +++ b/java/base/Functional.java @@ -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 List map(final Collection items, final Function f) { + return items.stream().map(f).collect(Collectors.toUnmodifiableList()); + } + + public static List map(final List items, final BiFunction f) { + return IntStream.range(0, items.size()) + .mapToObj(i -> f.apply(i, items.get(i))) + .collect(Collectors.toUnmodifiableList()); + } + + public static Map mapValues(final Map map, final Function f) { + return map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> f.apply(e.getValue()))); + } + + @SafeVarargs + public static Map mergeMaps(final Map... 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 List concat(final Collection... items) { + final List result = new ArrayList<>(); + for (final Collection item : items) { + result.addAll(item); + } + return result; + } + + public static List append(final Collection collection, final T item) { + final List list = new ArrayList<>(collection); + list.add(item); + return list; + } + + public static List> allValues(final List 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 V get(final Map 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 values, final int d, final int c) { + for (int i = -d; i <= d; i++) { + values.add(c + i); + } + } + + public static void forEachPair(final T[] items, final BiConsumer 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 List> 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(); + } +} diff --git a/java/base/Log.java b/java/base/Log.java new file mode 100644 index 0000000..00d9141 --- /dev/null +++ b/java/base/Log.java @@ -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 action(final Runnable action) { + return () -> { + action.run(); + return null; + }; + } + + public void scope(final String name, final Runnable action) { + scope(name, action(action)); + } + + public T scope(final String name, final Supplier action) { + println(name); + indent++; + try { + return silentScope(name, action); + } finally { + indent--; + } + } + + public T silentScope(final String ignoredName, final Supplier 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; + } +} diff --git a/java/base/MainChecker.java b/java/base/MainChecker.java new file mode 100644 index 0000000..e526e7e --- /dev/null +++ b/java/base/MainChecker.java @@ -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 run(final TestCounter counter, final String... input) { + return runner.run(counter, input); + } + + public List run(final TestCounter counter, final List input) { + return runner.run(counter, input); + } + + public void testEquals(final TestCounter counter, final List input, final List expected) { + runner.testEquals(counter, input, expected); + } + +} diff --git a/java/base/Named.java b/java/base/Named.java new file mode 100644 index 0000000..befb254 --- /dev/null +++ b/java/base/Named.java @@ -0,0 +1,15 @@ +package base; + +/** + * @author Georgiy Korneev (kgeorgiy@kgeorgiy.info) + */ +public record Named(String name, T value) { + public static Named of(final String name, final T f) { + return new Named<>(name, f); + } + + @Override + public String toString() { + return name; + } +} diff --git a/java/base/Pair.java b/java/base/Pair.java new file mode 100644 index 0000000..8c27a31 --- /dev/null +++ b/java/base/Pair.java @@ -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 first, S second) { + public static Pair of(final F first, final S second) { + return new Pair<>(first, second); + } + + public static Pair of(final Map.Entry e) { + return of(e.getKey(), e.getValue()); + } + + public static UnaryOperator> lift(final UnaryOperator f, final UnaryOperator s) { + return p -> of(f.apply(p.first), s.apply(p.second)); + } + + public static BinaryOperator> lift(final BinaryOperator f, final BinaryOperator s) { + return (p1, p2) -> of(f.apply(p1.first, p2.first), s.apply(p1.second, p2.second)); + } + + public static Function> tee( + final Function f, + final Function s + ) { + return t -> of(f.apply(t), s.apply(t)); + } + + @Override + public String toString() { + return "(" + first + ", " + second + ")"; + } + + public Pair second(final R second) { + return new Pair<>(first, second); + } +} diff --git a/java/base/Runner.java b/java/base/Runner.java new file mode 100644 index 0000000..c0a59bc --- /dev/null +++ b/java/base/Runner.java @@ -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 run(final TestCounter counter, final List input); + + default List run(final TestCounter counter, final String... input) { + return run(counter, List.of(input)); + } + + default void testEquals(final TestCounter counter, final List input, final List expected) { + counter.test(() -> { + final List 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 run(String comment, TestCounter counter, List input); + } + + final class Packages { + private final List packages; + + private Packages(final List 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 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 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 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 output = Files.readAllLines(ouf); + Files.delete(inf); + Files.delete(ouf); + return output; + }); + } + } +} diff --git a/java/base/Selector.java b/java/base/Selector.java new file mode 100644 index 0000000..dc119b9 --- /dev/null +++ b/java/base/Selector.java @@ -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 modes; + private final Set variantNames = new LinkedHashSet<>(); + private final Map> 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 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 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 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 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 Composite composite(final Class owner, final Function factory, final String... modes) { + return new Composite<>(owner, factory, (tester, counter) -> tester.test(), modes); + } + + public static Composite composite(final Class owner, final Function factory, final BiConsumer tester, final String... modes) { + return new Composite<>(owner, factory, tester, modes); + } + + public List getModes() { + return modes.isEmpty() ? List.of("~") : modes; + } + + public List getVariants() { + return List.copyOf(variants.keySet()); + } + + /** + * @author Georgiy Korneev (kgeorgiy@kgeorgiy.info) + */ + public static final class Composite { + private final Selector selector; + private final Function factory; + private final BiConsumer tester; + private List> base; + + private Composite(final Class owner, final Function factory, final BiConsumer tester, final String... modes) { + selector = new Selector(owner, modes); + this.factory = factory; + this.tester = tester; + } + + @SafeVarargs + public final Composite variant(final String name, final Consumer... parts) { + if ("Base".equalsIgnoreCase(name)) { + base = List.of(parts); + return v(name.toLowerCase()); + } else { + return v(name, parts); + } + } + + @SafeVarargs + private Composite v(final String name, final Consumer... parts) { + selector.variant(name, counter -> { + final V variant = factory.apply(counter); + for (final Consumer part : base) { + part.accept(variant); + } + for (final Consumer part : parts) { + part.accept(variant); + } + tester.accept(variant, counter); + }); + return this; + } + + public Selector selector() { + return selector; + } + } +} diff --git a/java/base/TestCounter.java b/java/base/TestCounter.java new file mode 100644 index 0000000..85fb9c9 --- /dev/null +++ b/java/base/TestCounter.java @@ -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 properties; + private final ExtendedRandom random; + + private final long start = System.currentTimeMillis(); + private int passed; + + public TestCounter(final Class owner, final int mode, final Map 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 void testForEach(final Iterable items, final Consumer action) { + for (final T item : items) { + test(() -> action.accept(item)); + } + } + + public T testV(final Supplier 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 call(final String message, final SupplierE 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 fail(final String format, final Object... args) { + return fail(Asserts.error(format, args)); + } + + public T fail(final Throwable throwable) { + return fail(throwable, "%s: %s", throwable.getClass().getSimpleName(), throwable.getMessage()); + } + + public 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 Either get(final SupplierE 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 extends Supplier> { + T getE() throws Exception; + + @Override + default Either get() { + try { + return Either.right(getE()); + } catch (final Exception e) { + return Either.left(e); + } + } + } + + @FunctionalInterface + public interface RunnableE extends SupplierE { + void run() throws Exception; + + @Override + default Void getE() throws Exception { + run(); + return null; + } + } +} diff --git a/java/base/Tester.java b/java/base/Tester.java new file mode 100644 index 0000000..d30260d --- /dev/null +++ b/java/base/Tester.java @@ -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(); + } +} diff --git a/java/base/Unit.java b/java/base/Unit.java new file mode 100644 index 0000000..290febf --- /dev/null +++ b/java/base/Unit.java @@ -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"; + } +} diff --git a/java/base/package-info.java b/java/base/package-info.java new file mode 100644 index 0000000..0055441 --- /dev/null +++ b/java/base/package-info.java @@ -0,0 +1,7 @@ +/** + * Common homeworks test classes + * of Introduction to Programming course. + * + * @author Georgiy Korneev (kgeorgiy@kgeorgiy.info) + */ +package base; \ No newline at end of file diff --git a/java/markup/AbstractList.java b/java/markup/AbstractList.java new file mode 100644 index 0000000..d082201 --- /dev/null +++ b/java/markup/AbstractList.java @@ -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 items; + private final String highlight; + private final String texBegin; + private final String texEnd; + + protected AbstractList( + List 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(""); + } + + @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); + } +} diff --git a/java/markup/AbstractMarkup.java b/java/markup/AbstractMarkup.java new file mode 100644 index 0000000..216689f --- /dev/null +++ b/java/markup/AbstractMarkup.java @@ -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 items; + private final String highlightMarkdown; + private final String highlightHtml; + private final String highlightTexOpen; + private final String highlightTexClose; + + protected AbstractMarkup( + List 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(""); + } + } + + @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); + } +} diff --git a/java/markup/ContainsInListItem.java b/java/markup/ContainsInListItem.java new file mode 100644 index 0000000..585b427 --- /dev/null +++ b/java/markup/ContainsInListItem.java @@ -0,0 +1,6 @@ +package markup; + +/** + * @author Nikita Doschennikov (me@fymio.us) + */ +public interface ContainsInListItem extends Markup {} diff --git a/java/markup/Emphasis.java b/java/markup/Emphasis.java new file mode 100644 index 0000000..c2d0feb --- /dev/null +++ b/java/markup/Emphasis.java @@ -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 items) { + super(items, "*", "em", "\\emph{", "}"); + } +} diff --git a/java/markup/Html.java b/java/markup/Html.java new file mode 100644 index 0000000..d8168d7 --- /dev/null +++ b/java/markup/Html.java @@ -0,0 +1,8 @@ +package markup; + +/** + * @author Nikita Doschennikov (me@fymio.us) + */ +public interface Html { + void toHtml(StringBuilder sb); +} diff --git a/java/markup/ListItem.java b/java/markup/ListItem.java new file mode 100644 index 0000000..259b4f2 --- /dev/null +++ b/java/markup/ListItem.java @@ -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 items) { + super(items, "", "li", "\\item ", ""); + } +} diff --git a/java/markup/Markdown.java b/java/markup/Markdown.java new file mode 100644 index 0000000..879897a --- /dev/null +++ b/java/markup/Markdown.java @@ -0,0 +1,8 @@ +package markup; + +/** + * @author Nikita Doschennikov (me@fymio.us) + */ +public interface Markdown { + void toMarkdown(StringBuilder sb); +} diff --git a/java/markup/Markup.java b/java/markup/Markup.java new file mode 100644 index 0000000..c0bdce8 --- /dev/null +++ b/java/markup/Markup.java @@ -0,0 +1,6 @@ +package markup; + +/** + * @author Nikita Doschennikov (me@fymio.us) + */ +public interface Markup extends Markdown, Html, Tex {} diff --git a/java/markup/MarkupListTest.java b/java/markup/MarkupListTest.java new file mode 100644 index 0000000..fbc8b79 --- /dev/null +++ b/java/markup/MarkupListTest.java @@ -0,0 +1,248 @@ +package markup; + +import base.Asserts; +import base.Selector; +import base.TestCounter; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; +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.Stream; + +/** + * @author Georgiy Korneev (kgeorgiy@kgeorgiy.info) + */ +public final class MarkupListTest { + public static final Consumer VARIANT = MarkupListTest.variant( + "Tex", Map.ofEntries( + Map.entry("

", "\\par{}"), Map.entry("

", ""), + Map.entry("", "\\emph{"), Map.entry("", "}"), + Map.entry("", "\\textbf{"), Map.entry("", "}"), + Map.entry("", "\\textst{"), Map.entry("", "}"), + Map.entry("
    ", "\\begin{itemize}"), Map.entry("
", "\\end{itemize}"), + Map.entry("
    ", "\\begin{enumerate}"), Map.entry("
", "\\end{enumerate}"), + Map.entry("
  • ", "\\item "), Map.entry("
  • ", "") + ) + ); + + + public static final Selector SELECTOR = new Selector(MarkupListTest.class) + .variant("3637", VARIANT) + .variant("3839", VARIANT) + .variant("4142", VARIANT) + .variant("4749", VARIANT) + + ; + + private MarkupListTest() { + } + + public static Consumer variant(final String name, final Map mapping) { + return MarkupTester.variant(MarkupListTest::test, name, mapping); + } + + private static void test(final MarkupTester.Checker checker) { + final Paragraph paragraph0 = new Paragraph(List.of(new Text("hello"))); + final String paragraph0Markup = "

    hello

    "; + + final Paragraph paragraph1 = new Paragraph(List.of( + new Strong(List.of( + new Text("1"), + new Strikeout(List.of( + new Text("2"), + new Emphasis(List.of( + new Text("3"), + new Text("4") + )), + new Text("5") + )), + new Text("6") + )) + )); + final String paragraph1Markup = "

    123456

    "; + + final Paragraph paragraph2 = new Paragraph(List.of(new Strong(List.of( + new Text("sdq"), + new Strikeout(List.of(new Emphasis(List.of(new Text("r"))), new Text("vavc"))), + new Text("zg"))) + )); + final String paragraph2Markup = "

    sdqrvavczg

    "; + + checker.test(paragraph0, paragraph0Markup); + checker.test(paragraph1, paragraph1Markup); + checker.test(paragraph2, paragraph2Markup); + + final ListItem li1 = new ListItem(List.of(new Paragraph(List.of(new Text("1.1"))), new Paragraph(List.of(new Text("1.2"))))); + final String li1Markup = "

    1.1

    1.2

    "; + final ListItem li2 = new ListItem(List.of(new Paragraph(List.of(new Text("2"))))); + final String li2Markup = "

    2

    "; + final ListItem pli1 = new ListItem(List.of(paragraph1)); + final ListItem pli2 = new ListItem(List.of(paragraph2)); + + final ListItem nestedUl = new ListItem(List.of(ul(li1, li2))); + final String nestedUlMarkup = ul(li1Markup, li2Markup); + + checker.test(ul(li1), ul(li1Markup)); + checker.test(ul(li2), ul(li2Markup)); + checker.test(ul(pli1), ul(paragraph1Markup)); + checker.test(ul(pli2), ul(paragraph2Markup)); + checker.test(ul(li1, li2), nestedUlMarkup); + checker.test(ul(pli1, pli2), ul(paragraph1Markup, paragraph2Markup)); + checker.test(ul(nestedUl), ul(nestedUlMarkup)); + + final ListItem nestedOl = new ListItem(List.of(ol(li1, li2))); + final String nestedOlMarkup = ol(li1Markup, li2Markup); + checker.test(ol(li1), ol(li1Markup)); + checker.test(ol(li2), ol(li2Markup)); + checker.test(ol(pli1), ol(paragraph1Markup)); + checker.test(ol(pli2), ol(paragraph2Markup)); + checker.test(ol(li1, li2), nestedOlMarkup); + checker.test(ol(pli1, pli2), ol(paragraph1Markup, paragraph2Markup)); + checker.test(ol(nestedOl), ol(nestedOlMarkup)); + + checker.test(ul(nestedUl, nestedOl), ul(nestedUlMarkup, nestedOlMarkup)); + checker.test(ol(nestedUl, nestedOl), ol(nestedUlMarkup, nestedOlMarkup)); + + checker.test( + ul(nestedUl, nestedOl, pli1, pli2), + ul(nestedUlMarkup, nestedOlMarkup, paragraph1Markup, paragraph2Markup) + ); + checker.test( + ol(nestedUl, nestedOl, pli1, pli2), + ol(nestedUlMarkup, nestedOlMarkup, paragraph1Markup, paragraph2Markup) + ); + + checker.test( + new Paragraph(List.of(new Strikeout(List.of(new Strong(List.of(new Strikeout(List.of(new Emphasis(List.of(new Strikeout(List.of(new Text("е"), new Text("г"), new Text("ц"))), new Strong(List.of(new Text("щэш"), new Text("игепы"), new Text("хм"))), new Strikeout(List.of(new Text("б"), new Text("е"))))), new Strong(List.of(new Strong(List.of(new Text("ю"), new Text("дърб"), new Text("еи"))), new Emphasis(List.of(new Text("зр"), new Text("дуаужш"), new Text("ш"))), new Strong(List.of(new Text("рб"), new Text("щ"))))), new Text("a"))), new Strikeout(List.of(new Text("no"), new Text("ddw"), new Strong(List.of(new Emphasis(List.of(new Text("щ"), new Text("ча"), new Text("эгфш"))), new Strikeout(List.of(new Text("фяи"), new Text("штел"), new Text("н"))), new Strikeout(List.of(new Text("ту"), new Text("ьъг"))))))), new Emphasis(List.of(new Emphasis(List.of(new Text("tc"), new Strong(List.of(new Text("щ"), new Text("э"), new Text("то"))), new Strong(List.of(new Text("а"), new Text("ц"))))), new Emphasis(List.of(new Text("hld"), new Emphasis(List.of(new Text("ыо"), new Text("яще"), new Text("лэ"))), new Text("i"))), new Text("tm"))))), new Emphasis(List.of(new Text("q"), new Emphasis(List.of(new Text("zn"), new Strong(List.of(new Text("mnphd"), new Strong(List.of(new Text("г"), new Text("вй"), new Text("шш"))), new Strong(List.of(new Text("з"), new Text("ввъ"))))), new Strikeout(List.of(new Emphasis(List.of(new Text("у"), new Text("в"), new Text("у"))), new Strikeout(List.of(new Text("лдяр"), new Text("зоъ"), new Text("эн"))), new Strikeout(List.of(new Text("в"), new Text("м"))))))), new Strikeout(List.of(new Text("cqqzbhtn"), new Text("i"), new Strong(List.of(new Text("i"), new Strikeout(List.of(new Text("э"), new Text("як"))), new Text("i"))))))), new Text("ef"))), new Strikeout(List.of(new Strikeout(List.of(new Strong(List.of(new Emphasis(List.of(new Strong(List.of(new Text("шец"), new Text("ю"), new Text("дрк"))), new Strikeout(List.of(new Text("е"), new Text("мь"), new Text("б"))), new Strong(List.of(new Text("еп"), new Text("ряэк"))))), new Strong(List.of(new Text("t"), new Emphasis(List.of(new Text("сы"), new Text("в"), new Text("к"))), new Text("rf"))), new Text("x"))), new Emphasis(List.of(new Emphasis(List.of(new Emphasis(List.of(new Text("юд"), new Text("чх"), new Text("яжюи"))), new Emphasis(List.of(new Text("и"), new Text("п"), new Text("вх"))), new Text("mf"))), new Emphasis(List.of(new Strong(List.of(new Text("шб"), new Text("вс"), new Text("е"))), new Strong(List.of(new Text("т"), new Text("шж"), new Text("ину"))), new Strong(List.of(new Text("ыа"), new Text("ьскю"))))), new Text("x"))), new Strikeout(List.of(new Emphasis(List.of(new Strong(List.of(new Text("в"), new Text("зыйгг"), new Text("о"))), new Strikeout(List.of(new Text("ок"), new Text("уч"), new Text("л"))), new Text("v"))), new Emphasis(List.of(new Strong(List.of(new Text("н"), new Text("ъчжфзтодг"), new Text("кыч"))), new Strikeout(List.of(new Text("вд"), new Text("лпбзс"), new Text("гщ"))), new Emphasis(List.of(new Text("ъ"), new Text("й"))))), new Text("n"))))), new Strong(List.of(new Strong(List.of(new Emphasis(List.of(new Strong(List.of(new Text("ю"), new Text("сдям"), new Text("ш"))), new Strong(List.of(new Text("ц"), new Text("еящж"), new Text("шн"))), new Text("upg"))), new Text("d"), new Strikeout(List.of(new Text("xu"), new Strikeout(List.of(new Text("кл"), new Text("еок"), new Text("с"))), new Strong(List.of(new Text("а"), new Text("ь"))))))), new Strong(List.of(new Strikeout(List.of(new Text("zn"), new Text("syb"), new Strong(List.of(new Text("ъзюкмц"), new Text("ндюз"))))), new Strong(List.of(new Strikeout(List.of(new Text("н"), new Text("с"), new Text("ь"))), new Strikeout(List.of(new Text("зьуес"), new Text("к"), new Text("и"))), new Strong(List.of(new Text("тв"), new Text("у"))))), new Strikeout(List.of(new Strong(List.of(new Text("ы"), new Text("г"), new Text("гм"))), new Strong(List.of(new Text("сыр"), new Text("я"), new Text("т"))), new Emphasis(List.of(new Text("ь"), new Text("махыы"))))))), new Text("k"))), new Text("q"))), new Strikeout(List.of(new Text("b"), new Text("o"), new Emphasis(List.of(new Strong(List.of(new Strikeout(List.of(new Strong(List.of(new Text("х"), new Text("йз"), new Text("ж"))), new Text("udlh"), new Strikeout(List.of(new Text("чъ"), new Text("с"))))), new Strong(List.of(new Strong(List.of(new Text("ю"), new Text("т"), new Text("яъайл"))), new Strong(List.of(new Text("х"), new Text("ри"), new Text("в"))), new Strong(List.of(new Text("щ"), new Text("вт"))))), new Text("m"))), new Text("vzb"), new Strong(List.of(new Text("oi"), new Text("r"), new Text("inpz"))))))))), + "

    егцщэшигепыхмбеюдърбеизрдуаужшшрбщanoddwщчаэгфшфяиштелнтуьъгtcщэтоацhldыоящелэitmqznmnphdгвйшшзввъувулдярзоъэнвмcqqzbhtniiэякiefшецюдркемьбепряэкtсывкrfxюдчхяжюиипвхmfшбвсетшжинуыаьскюxвзыйггоокучлvнъчжфзтодгкычвдлпбзсгщъйnюсдямшцеящжшнupgdxuклеоксаьznsybъзюкмцндюзнсьзьуескитвуыггмсырятьмахыыkqboхйзжudlhчъсютяъайлхривщвтmvzboirinpz

    " + ); + + checker.test( + new OrderedList(List.of(new ListItem(List.of(new OrderedList(List.of(new ListItem(List.of(new OrderedList(List.of()), new Paragraph(List.of(new Text("е"))), new Paragraph(List.of(new Text("х"))))), new ListItem(List.of(new OrderedList(List.of()), new OrderedList(List.of()), new Paragraph(List.of(new Text("эш"))))), new ListItem(List.of(new UnorderedList(List.of()), new Paragraph(List.of(new Text("цць"))))), new ListItem(List.of(new UnorderedList(List.of()), new Paragraph(List.of(new Text("м"))))))), new UnorderedList(List.of(new ListItem(List.of(new OrderedList(List.of()), new OrderedList(List.of()), new OrderedList(List.of()))), new ListItem(List.of(new Paragraph(List.of(new Text("ю"))), new UnorderedList(List.of()), new UnorderedList(List.of()))), new ListItem(List.of(new OrderedList(List.of()), new OrderedList(List.of()))), new ListItem(List.of(new UnorderedList(List.of()), new UnorderedList(List.of()))))), new Paragraph(List.of(new Emphasis(List.of(new Emphasis(List.of(new Text("узр"))), new Text("i"), new Emphasis(List.of(new Text("аужш"))), new Text("ш"))), new Strong(List.of(new Text("c"), new Strikeout(List.of(new Text("щ"))), new Text("a"), new Text("з"))), new Strong(List.of(new Emphasis(List.of(new Text("ь"))), new Text("ddw"), new Text("зщ"), new Text("ча"))), new Emphasis(List.of(new Strong(List.of(new Text("гфш"))), new Strikeout(List.of(new Text("фяи"))), new Text("штел"), new Text("н"))))), new OrderedList(List.of(new ListItem(List.of(new UnorderedList(List.of()), new OrderedList(List.of()), new UnorderedList(List.of()))), new ListItem(List.of(new OrderedList(List.of()), new Paragraph(List.of(new Text("юцщ"))), new UnorderedList(List.of()))), new ListItem(List.of(new UnorderedList(List.of()), new UnorderedList(List.of()))), new ListItem(List.of(new OrderedList(List.of()), new Paragraph(List.of(new Text("э"))))))))), new ListItem(List.of(new OrderedList(List.of(new ListItem(List.of(new UnorderedList(List.of()), new OrderedList(List.of()), new Paragraph(List.of(new Text("ж"))))), new ListItem(List.of(new OrderedList(List.of()), new Paragraph(List.of(new Text("ыеж"))), new Paragraph(List.of(new Text("ыо"))))), new ListItem(List.of(new Paragraph(List.of(new Text("ще"))), new Paragraph(List.of(new Text("щш"))))), new ListItem(List.of(new UnorderedList(List.of()), new OrderedList(List.of()))))), new OrderedList(List.of(new ListItem(List.of(new Paragraph(List.of(new Text("щосз"))), new OrderedList(List.of()), new UnorderedList(List.of()))), new ListItem(List.of(new UnorderedList(List.of()), new OrderedList(List.of()), new UnorderedList(List.of()))), new ListItem(List.of(new UnorderedList(List.of()), new UnorderedList(List.of()))), new ListItem(List.of(new Paragraph(List.of(new Text("сс"))), new UnorderedList(List.of()))))), new Paragraph(List.of(new Text("yu"), new Text("w"), new Text("ghtry"), new Emphasis(List.of(new Strikeout(List.of(new Text("прф"))), new Emphasis(List.of(new Text("р"))), new Text("я"), new Text("я"))))), new Paragraph(List.of(new Text("w"), new Strong(List.of(new Text("k"), new Emphasis(List.of(new Text("н"))), new Strikeout(List.of(new Text("в"))), new Text("м"))), new Strikeout(List.of(new Text("cqqzbhtn"), new Text("i"), new Text("м"), new Text("ю"))), new Strikeout(List.of(new Strong(List.of(new Text("ш"))), new Strong(List.of(new Text("к"))), new Text("ж"), new Text("б"))))))), new ListItem(List.of(new UnorderedList(List.of(new ListItem(List.of(new OrderedList(List.of()), new OrderedList(List.of()), new UnorderedList(List.of()))), new ListItem(List.of(new UnorderedList(List.of()), new OrderedList(List.of()), new UnorderedList(List.of()))), new ListItem(List.of(new OrderedList(List.of()), new UnorderedList(List.of()))), new ListItem(List.of(new UnorderedList(List.of()), new UnorderedList(List.of()))))), new UnorderedList(List.of(new ListItem(List.of(new UnorderedList(List.of()), new OrderedList(List.of()), new OrderedList(List.of()))), new ListItem(List.of(new UnorderedList(List.of()), new Paragraph(List.of(new Text("е"))), new UnorderedList(List.of()))), new ListItem(List.of(new OrderedList(List.of()), new UnorderedList(List.of()))), new ListItem(List.of(new Paragraph(List.of(new Text("ед"))), new UnorderedList(List.of()))))), new OrderedList(List.of(new ListItem(List.of(new OrderedList(List.of()), new OrderedList(List.of()), new Paragraph(List.of(new Text("п"))))), new ListItem(List.of(new UnorderedList(List.of()), new Paragraph(List.of(new Text("э"))), new UnorderedList(List.of()))), new ListItem(List.of(new UnorderedList(List.of()), new UnorderedList(List.of()))), new ListItem(List.of(new OrderedList(List.of()), new Paragraph(List.of(new Text("к"))))))), new Paragraph(List.of(new Strong(List.of(new Strong(List.of(new Text("с"))), new Text("x"), new Emphasis(List.of(new Text("йюд"))), new Text("чх"))), new Strikeout(List.of(new Strong(List.of(new Text("жюи"))), new Emphasis(List.of(new Text("и"))), new Strong(List.of(new Text("ьмт"))), new Text("йц"))), new Emphasis(List.of(new Strong(List.of(new Text("шб"))), new Strong(List.of(new Text("еф"))), new Text("ут"), new Text("шж"))), new Emphasis(List.of(new Emphasis(List.of(new Text("ну"))), new Strong(List.of(new Text("ыа"))), new Text("ьскю"), new Text("чз"))))))), new ListItem(List.of(new UnorderedList(List.of(new ListItem(List.of(new OrderedList(List.of()), new OrderedList(List.of()), new Paragraph(List.of(new Text("ыйгг"))))), new ListItem(List.of(new OrderedList(List.of()), new UnorderedList(List.of()), new Paragraph(List.of(new Text("ф"))))), new ListItem(List.of(new OrderedList(List.of()), new Paragraph(List.of(new Text("ч"))))), new ListItem(List.of(new OrderedList(List.of()), new OrderedList(List.of()))))), new Paragraph(List.of(new Strikeout(List.of(new Emphasis(List.of(new Text("э"))), new Text("amqcfdzrg"), new Emphasis(List.of(new Text("т"))), new Text("з"))), new Text("b"), new Emphasis(List.of(new Strikeout(List.of(new Text("энфны"))), new Strikeout(List.of(new Text("гщ"))), new Text("ы"), new Text("шя"))), new Text("uvpqzhn"))), new UnorderedList(List.of(new ListItem(List.of(new UnorderedList(List.of()), new OrderedList(List.of()), new OrderedList(List.of()))), new ListItem(List.of(new OrderedList(List.of()), new Paragraph(List.of(new Text("ящж"))), new UnorderedList(List.of()))), new ListItem(List.of(new OrderedList(List.of()), new Paragraph(List.of(new Text("цлл"))))), new ListItem(List.of(new OrderedList(List.of()), new Paragraph(List.of(new Text("ъ"))))))), new Paragraph(List.of(new Strong(List.of(new Strong(List.of(new Text("ъ"))), new Strikeout(List.of(new Text("кл"))), new Strikeout(List.of(new Text("счи"))), new Text("ра"))), new Strong(List.of(new Strikeout(List.of(new Text("ь"))), new Text("zn"), new Text("ъ"), new Text("умъъзюкмц"))), new Strikeout(List.of(new Emphasis(List.of(new Text("дюз"))), new Strong(List.of(new Text("эы"))), new Text("и"), new Text("р"))), new Emphasis(List.of(new Strong(List.of(new Text("ьуес"))), new Strikeout(List.of(new Text("йгтв"))), new Text("у"), new Text("еы"))))))))), + "
        1. е

          х

            1. эш

              • цць

                • м

                      1. ю

                                  узрiаужшшcщьddwзщчагфшфяиштелн

                                          1. юцщ

                                                  1. э

                                                      1. ж

                                                        1. ыеж

                                                          ыо

                                                        2. ще

                                                          щш

                                                            1. щосз

                                                                          • сс

                                                                            yuwghtryпрфряя

                                                                            wkнвмcqqzbhtniмюшкжб

                                                                                                        • е

                                                                                                              • ед

                                                                                                                    1. п

                                                                                                                      • э

                                                                                                                              1. к

                                                                                                                              сxйюдчхжюииьмтйцшбефутшжнуыаьскючз

                                                                                                                                  1. ыйгг

                                                                                                                                      • ф

                                                                                                                                        1. ч

                                                                                                                                            эamqcfdzrgтзbэнфныгщышяuvpqzhn

                                                                                                                                                    1. ящж

                                                                                                                                                        1. цлл

                                                                                                                                                          1. ъ

                                                                                                                                                          ъклсчираьznъумъъзюкмцдюзэыирьуесйгтвуеы

                                                                                                                                                        " + ); + + checker.test( + new UnorderedList(List.of(new ListItem(List.of(new OrderedList(List.of(new ListItem(List.of(new OrderedList(List.of()), new Paragraph(List.of(new Text("е"))))), new ListItem(List.of(new UnorderedList(List.of()), new OrderedList(List.of()))), new ListItem(List.of(new OrderedList(List.of()), new OrderedList(List.of()))), new ListItem(List.of(new Paragraph(List.of(new Text("нцйцць"))), new OrderedList(List.of()))), new ListItem(List.of(new Paragraph(List.of(new Text("м"))))))), new UnorderedList(List.of(new ListItem(List.of(new OrderedList(List.of()), new OrderedList(List.of()))), new ListItem(List.of(new OrderedList(List.of()), new Paragraph(List.of(new Text("ю"))))), new ListItem(List.of(new UnorderedList(List.of()), new Paragraph(List.of(new Text("щ"))))), new ListItem(List.of(new UnorderedList(List.of()), new UnorderedList(List.of()))), new ListItem(List.of(new UnorderedList(List.of()))))), new Paragraph(List.of(new Strikeout(List.of(new Emphasis(List.of(new Text("зр"))), new Text("i"), new Text("и"), new Text("г"), new Text("с"))), new Strong(List.of(new Strong(List.of(new Text("шмрб"))), new Strong(List.of(new Text("ь"))), new Text("з"), new Text("з"), new Text("фь"))), new Text("ddw"), new Strong(List.of(new Emphasis(List.of(new Text("щ"))), new Strong(List.of(new Text("втъп"))), new Text("ш"), new Text("ч"), new Text("фяи"))), new Strong(List.of(new Emphasis(List.of(new Text("тел"))), new Text("н"), new Text("ь"), new Text("ддзюцщ"), new Text("пт"))))), new Paragraph(List.of(new Text("n"), new Text("zi"), new Strong(List.of(new Emphasis(List.of(new Text("ж"))), new Text("t"), new Text("ыеж"), new Text("ч"), new Text("г"))), new Text("kwt"), new Strong(List.of(new Strong(List.of(new Text("э"))), new Text("нх"), new Text("уи"), new Text("о"), new Text("п"))))), new UnorderedList(List.of(new ListItem(List.of(new OrderedList(List.of()), new OrderedList(List.of()))), new ListItem(List.of(new Paragraph(List.of(new Text("ж"))), new UnorderedList(List.of()))), new ListItem(List.of(new UnorderedList(List.of()), new UnorderedList(List.of()))), new ListItem(List.of(new Paragraph(List.of(new Text("сс"))))), new ListItem(List.of(new Paragraph(List.of(new Text("т"))))))))), new ListItem(List.of(new UnorderedList(List.of(new ListItem(List.of(new UnorderedList(List.of()), new UnorderedList(List.of()))), new ListItem(List.of(new UnorderedList(List.of()), new UnorderedList(List.of()))), new ListItem(List.of(new OrderedList(List.of()), new OrderedList(List.of()))), new ListItem(List.of(new OrderedList(List.of()), new UnorderedList(List.of()))), new ListItem(List.of(new Paragraph(List.of(new Text("щу"))))))), new OrderedList(List.of(new ListItem(List.of(new OrderedList(List.of()), new Paragraph(List.of(new Text("ир"))))), new ListItem(List.of(new OrderedList(List.of()), new OrderedList(List.of()))), new ListItem(List.of(new Paragraph(List.of(new Text("зоъ"))), new Paragraph(List.of(new Text("е"))))), new ListItem(List.of(new Paragraph(List.of(new Text("в"))), new UnorderedList(List.of()))), new ListItem(List.of(new UnorderedList(List.of()))))), new OrderedList(List.of(new ListItem(List.of(new Paragraph(List.of(new Text("сснюпия"))), new Paragraph(List.of(new Text("щ"))))), new ListItem(List.of(new OrderedList(List.of()), new UnorderedList(List.of()))), new ListItem(List.of(new Paragraph(List.of(new Text("э"))), new OrderedList(List.of()))), new ListItem(List.of(new OrderedList(List.of()), new OrderedList(List.of()))), new ListItem(List.of(new OrderedList(List.of()))))), new UnorderedList(List.of(new ListItem(List.of(new Paragraph(List.of(new Text("м"))), new OrderedList(List.of()))), new ListItem(List.of(new UnorderedList(List.of()), new UnorderedList(List.of()))), new ListItem(List.of(new OrderedList(List.of()), new UnorderedList(List.of()))), new ListItem(List.of(new OrderedList(List.of()), new UnorderedList(List.of()))), new ListItem(List.of(new UnorderedList(List.of()))))), new UnorderedList(List.of(new ListItem(List.of(new UnorderedList(List.of()), new UnorderedList(List.of()))), new ListItem(List.of(new OrderedList(List.of()), new OrderedList(List.of()))), new ListItem(List.of(new Paragraph(List.of(new Text("е"))), new UnorderedList(List.of()))), new ListItem(List.of(new OrderedList(List.of()))), new ListItem(List.of(new UnorderedList(List.of()))))))), new ListItem(List.of(new Paragraph(List.of(new Strong(List.of(new Emphasis(List.of(new Text("п"))), new Text("l"), new Text("р"), new Text("п"), new Text("уерсы"))), new Strikeout(List.of(new Strikeout(List.of(new Text("к"))), new Text("rf"), new Text("екйюд"), new Text("чх"), new Text("яжюи"))), new Emphasis(List.of(new Strikeout(List.of(new Text("кьмт"))), new Strikeout(List.of(new Text("рщюереф"))), new Text("ут"), new Text("шж"), new Text("ину"))), new Strong(List.of(new Strong(List.of(new Text("дгб"))), new Emphasis(List.of(new Text("кю"))), new Text("чз"), new Text("мв"), new Text("зыйгг"))), new Strong(List.of(new Strikeout(List.of(new Text("ш"))), new Text("ф"), new Text("я"), new Text("ч"), new Text("ме"))))), new Paragraph(List.of(new Strikeout(List.of(new Emphasis(List.of(new Text("э"))), new Text("amqcfdzrg"), new Text("кыч"), new Text("к"), new Text("я"))), new Strikeout(List.of(new Strong(List.of(new Text("нфны"))), new Strikeout(List.of(new Text("гщ"))), new Text("ы"), new Text("шя"), new Text("е"))), new Strong(List.of(new Strong(List.of(new Text("ъю"))), new Emphasis(List.of(new Text("яхе"))), new Text("б"), new Text("бц"), new Text("еящж"))), new Text("cn"), new Emphasis(List.of(new Strong(List.of(new Text("як"))), new Text("въ"), new Text("оде"), new Text("кл"), new Text("еок"))))), new Paragraph(List.of(new Strikeout(List.of(new Strong(List.of(new Text("а"))), new Strong(List.of(new Text("иь"))), new Text("аш"), new Text("ъ"), new Text("умъъзюкмц"))), new Strikeout(List.of(new Emphasis(List.of(new Text("дюз"))), new Strong(List.of(new Text("эы"))), new Text("и"), new Text("р"), new Text("зьуес"))), new Strikeout(List.of(new Strikeout(List.of(new Text("и"))), new Strong(List.of(new Text("тв"))), new Text("у"), new Text("еы"), new Text("г"))), new Text("atsui"), new Strikeout(List.of(new Text("y"), new Text("щз"), new Text("н"), new Text("е"), new Text("э"))))), new Paragraph(List.of(new Emphasis(List.of(new Text("o"), new Text("rz"), new Text("к"), new Text("к"), new Text("б"))), new Emphasis(List.of(new Strong(List.of(new Text("ьх"))), new Emphasis(List.of(new Text("ил"))), new Text("ф"), new Text("пмгр"), new Text("и"))), new Emphasis(List.of(new Text("lhovy"), new Emphasis(List.of(new Text("ъайл"))), new Text("ь"), new Text("э"), new Text("п"))), new Strikeout(List.of(new Strong(List.of(new Text("щщ"))), new Strong(List.of(new Text("х"))), new Text("б"), new Text("е"), new Text("к"))), new Emphasis(List.of(new Strikeout(List.of(new Text("чяя"))), new Text("х"), new Text("я"), new Text("р"), new Text("ю"))))), new Paragraph(List.of(new Strikeout(List.of(new Emphasis(List.of(new Text("йл"))), new Emphasis(List.of(new Text("змл"))), new Text("б"), new Text("аж"), new Text("ъ"))), new Strong(List.of(new Strong(List.of(new Text("энян"))), new Emphasis(List.of(new Text("ю"))), new Text("п"), new Text("ымы"), new Text("ешьи"))), new Emphasis(List.of(new Strong(List.of(new Text("к"))), new Strikeout(List.of(new Text("яэ"))), new Text("п"), new Text("юзщ"), new Text("я"))), new Text("w"), new Emphasis(List.of(new Text("se"), new Text("о"), new Text("ъязе"), new Text("гзко"), new Text("ъ"))))))), new ListItem(List.of(new OrderedList(List.of(new ListItem(List.of(new Paragraph(List.of(new Text("ч"))), new Paragraph(List.of(new Text("пз"))))), new ListItem(List.of(new OrderedList(List.of()), new Paragraph(List.of(new Text("й"))))), new ListItem(List.of(new Paragraph(List.of(new Text("лчж"))), new Paragraph(List.of(new Text("чв"))))), new ListItem(List.of(new Paragraph(List.of(new Text("с"))), new OrderedList(List.of()))), new ListItem(List.of(new OrderedList(List.of()))))), new UnorderedList(List.of(new ListItem(List.of(new Paragraph(List.of(new Text("ь"))), new Paragraph(List.of(new Text("ъ"))))), new ListItem(List.of(new OrderedList(List.of()), new UnorderedList(List.of()))), new ListItem(List.of(new UnorderedList(List.of()), new UnorderedList(List.of()))), new ListItem(List.of(new Paragraph(List.of(new Text("вп"))), new Paragraph(List.of(new Text("р"))))), new ListItem(List.of(new OrderedList(List.of()))))), new Paragraph(List.of(new Text("ds"), new Emphasis(List.of(new Strikeout(List.of(new Text("дйгып"))), new Emphasis(List.of(new Text("и"))), new Text("сэ"), new Text("е"), new Text("юо"))), new Emphasis(List.of(new Strikeout(List.of(new Text("бвщ"))), new Text("d"), new Text("ъ"), new Text("ит"), new Text("бщ"))), new Emphasis(List.of(new Text("w"), new Strikeout(List.of(new Text("гсщ"))), new Text("ъ"), new Text("срцч"), new Text("хе"))), new Text("m"))), new OrderedList(List.of(new ListItem(List.of(new UnorderedList(List.of()), new OrderedList(List.of()))), new ListItem(List.of(new UnorderedList(List.of()), new UnorderedList(List.of()))), new ListItem(List.of(new OrderedList(List.of()), new UnorderedList(List.of()))), new ListItem(List.of(new Paragraph(List.of(new Text("е"))), new OrderedList(List.of()))), new ListItem(List.of(new UnorderedList(List.of()))))), new UnorderedList(List.of(new ListItem(List.of(new OrderedList(List.of()), new UnorderedList(List.of()))), new ListItem(List.of(new Paragraph(List.of(new Text("оото"))), new OrderedList(List.of()))), new ListItem(List.of(new OrderedList(List.of()), new OrderedList(List.of()))), new ListItem(List.of(new UnorderedList(List.of()))), new ListItem(List.of(new UnorderedList(List.of()))))))), new ListItem(List.of(new Paragraph(List.of(new Emphasis(List.of(new Emphasis(List.of(new Text("я"))), new Strong(List.of(new Text("сшъ"))), new Text("лм"), new Text("ы"), new Text("рц"))), new Emphasis(List.of(new Strikeout(List.of(new Text("я"))), new Strikeout(List.of(new Text("ъ"))), new Text("п"), new Text("дхдэ"), new Text("щэ"))), new Emphasis(List.of(new Text("dtt"), new Emphasis(List.of(new Text("дрм"))), new Text("в"), new Text("яешц"), new Text("йшй"))), new Strong(List.of(new Strong(List.of(new Text("мив"))), new Text("u"), new Text("у"), new Text("к"), new Text("б"))), new Strikeout(List.of(new Text("c"), new Text("э"), new Text("м"), new Text("п"), new Text("о"))))), new UnorderedList(List.of(new ListItem(List.of(new OrderedList(List.of()), new OrderedList(List.of()))), new ListItem(List.of(new Paragraph(List.of(new Text("х"))), new Paragraph(List.of(new Text("й"))))), new ListItem(List.of(new Paragraph(List.of(new Text("эя"))), new UnorderedList(List.of()))), new ListItem(List.of(new UnorderedList(List.of()), new Paragraph(List.of(new Text("ф"))))), new ListItem(List.of(new OrderedList(List.of()))))), new OrderedList(List.of(new ListItem(List.of(new OrderedList(List.of()), new Paragraph(List.of(new Text("щ"))))), new ListItem(List.of(new Paragraph(List.of(new Text("чи"))), new OrderedList(List.of()))), new ListItem(List.of(new Paragraph(List.of(new Text("к"))), new OrderedList(List.of()))), new ListItem(List.of(new UnorderedList(List.of()), new OrderedList(List.of()))), new ListItem(List.of(new Paragraph(List.of(new Text("ф"))))))), new OrderedList(List.of(new ListItem(List.of(new OrderedList(List.of()), new UnorderedList(List.of()))), new ListItem(List.of(new Paragraph(List.of(new Text("м"))), new Paragraph(List.of(new Text("щцс"))))), new ListItem(List.of(new Paragraph(List.of(new Text("вус"))), new Paragraph(List.of(new Text("я"))))), new ListItem(List.of(new Paragraph(List.of(new Text("кр"))))), new ListItem(List.of(new UnorderedList(List.of()))))), new UnorderedList(List.of(new ListItem(List.of(new UnorderedList(List.of()), new Paragraph(List.of(new Text("я"))))), new ListItem(List.of(new UnorderedList(List.of()), new Paragraph(List.of(new Text("гр"))))), new ListItem(List.of(new Paragraph(List.of(new Text("ж"))), new UnorderedList(List.of()))), new ListItem(List.of(new UnorderedList(List.of()))), new ListItem(List.of(new OrderedList(List.of()))))))))), + "
                                                                                                                                                            1. е

                                                                                                                                                                    1. нцйцць

                                                                                                                                                                      1. м

                                                                                                                                                                            1. ю

                                                                                                                                                                              • щ

                                                                                                                                                                                    зрiигсшмрбьззфьddwщвтъпшчфяителньддзюцщпт

                                                                                                                                                                                    nziжtыежчгkwtэнхуиоп

                                                                                                                                                                                        1. ж

                                                                                                                                                                                              • сс

                                                                                                                                                                                              • т

                                                                                                                                                                                                              • щу

                                                                                                                                                                                                                1. ир

                                                                                                                                                                                                                    1. зоъ

                                                                                                                                                                                                                      е

                                                                                                                                                                                                                    2. в

                                                                                                                                                                                                                        1. сснюпия

                                                                                                                                                                                                                          щ

                                                                                                                                                                                                                            • э

                                                                                                                                                                                                                                    • м

                                                                                                                                                                                                                                                            1. е

                                                                                                                                                                                                                                                                1. пlрпуерсыкrfекйюдчхяжюикьмтрщюерефутшжинудгбкючзмвзыйггшфячме

                                                                                                                                                                                                                                                                  эamqcfdzrgкычкянфныгщышяеъюяхеббцеящжcnяквъодеклеок

                                                                                                                                                                                                                                                                  аиьашъумъъзюкмцдюзэыирзьуеситвуеыгatsuiyщзнеэ

                                                                                                                                                                                                                                                                  orzккбьхилфпмгриlhovyъайльэпщщхбекчяяхярю

                                                                                                                                                                                                                                                                  йлзмлбажъэнянюпымыешьикяэпюзщяwseоъязегзкоъ

                                                                                                                                                                                                                                                                  1. ч

                                                                                                                                                                                                                                                                    пз

                                                                                                                                                                                                                                                                    1. й

                                                                                                                                                                                                                                                                    2. лчж

                                                                                                                                                                                                                                                                      чв

                                                                                                                                                                                                                                                                    3. с

                                                                                                                                                                                                                                                                        • ь

                                                                                                                                                                                                                                                                          ъ

                                                                                                                                                                                                                                                                                • вп

                                                                                                                                                                                                                                                                                  р

                                                                                                                                                                                                                                                                                  dsдйгыписэеюобвщdъитбщwгсщъсрцчхеm

                                                                                                                                                                                                                                                                                              • е

                                                                                                                                                                                                                                                                                                      • оото

                                                                                                                                                                                                                                                                                                              • ясшълмырцяъпдхдэщэdttдрмвяешцйшймивuукбcэмпо

                                                                                                                                                                                                                                                                                                                    1. х

                                                                                                                                                                                                                                                                                                                      й

                                                                                                                                                                                                                                                                                                                    2. эя

                                                                                                                                                                                                                                                                                                                        • ф

                                                                                                                                                                                                                                                                                                                            1. щ

                                                                                                                                                                                                                                                                                                                            2. чи

                                                                                                                                                                                                                                                                                                                              1. к

                                                                                                                                                                                                                                                                                                                                    1. ф

                                                                                                                                                                                                                                                                                                                                        • м

                                                                                                                                                                                                                                                                                                                                          щцс

                                                                                                                                                                                                                                                                                                                                        • вус

                                                                                                                                                                                                                                                                                                                                          я

                                                                                                                                                                                                                                                                                                                                        • кр

                                                                                                                                                                                                                                                                                                                                            • я

                                                                                                                                                                                                                                                                                                                                              • гр

                                                                                                                                                                                                                                                                                                                                              • ж

                                                                                                                                                                                                                                                                                                                                                  " + ); + + checkTypes(); + } + + private static OrderedList ol(final ListItem... items) { + return new OrderedList(List.of(items)); + } + + private static String ol(final String... items) { + return list("ol", items); + } + + private static UnorderedList ul(final ListItem... items) { + return new UnorderedList(List.of(items)); + } + + private static String ul(final String... items) { + return list("ul", items); + } + + private static String list(final String type, final String[] items) { + return "<" + type + ">" + Stream.of(items).map(item -> "
                                                                                                                                                                                                                                                                                                                                                • " + item + "
                                                                                                                                                                                                                                                                                                                                                • ").collect(Collectors.joining()) + ""; + } + + private static Class loadClass(final String name) { + try { + return Class.forName(name); + } catch (final ClassNotFoundException e) { + throw Asserts.error("Cannot find class %s: %s", name, e); + } + } + + private static Map> loadClasses(final String... names) { + return Arrays.stream(names) + .collect(Collectors.toUnmodifiableMap(Function.identity(), name -> loadClass("markup." + name))); + } + + private static void checkTypes() { + final Map> classes = loadClasses("Text", "Emphasis", "Strikeout", "Strong", "Paragraph", "OrderedList", "UnorderedList", "ListItem"); + final String[] inlineClasses = {"Text", "Emphasis", "Strikeout", "Strong"}; + + checkConstructor(classes, "OrderedList", "ListItem"); + checkConstructor(classes, "UnorderedList", "ListItem"); + checkConstructor(classes, "ListItem", "OrderedList", "UnorderedList", "Paragraph"); + Stream.of("Paragraph", "Emphasis", "Strong", "Strikeout") + .forEach(parent -> checkConstructor(classes, parent, inlineClasses)); + } + + private static void checkConstructor(final Map> classes, final String parent, final String... children) { + new TypeChecker(classes, parent, children).checkConstructor(); + } + + private static class TypeChecker { + private final Map> classes; + private final Set> children; + private final Class parent; + + public TypeChecker(final Map> classes, final String parent, final String[] children) { + this.classes = classes; + this.children = Arrays.stream(children).map(classes::get).collect(Collectors.toUnmodifiableSet()); + this.parent = Objects.requireNonNull(classes.get(parent)); + } + + private void checkClassType(final Class classType) { + final Predicate> isAssignableFrom = classType::isAssignableFrom; + checkType(parent, Predicate.not(isAssignableFrom), "not ", children.stream()); + checkType(parent, isAssignableFrom, "", classes.values().stream().filter(Predicate.not(children::contains))); + } + + private static void checkType(final Class parent, final Predicate> predicate, final String not, final Stream> children) { + children.filter(predicate).findAny().ifPresent(child -> { + throw Asserts.error("%s is %scompatible with child of type %s", parent, not, child); + }); + } + + @SuppressWarnings("ChainOfInstanceofChecks") + private void checkParametrizedType(final ParameterizedType type) { + final Type actualType = type.getActualTypeArguments()[0]; + if (actualType instanceof Class) { + checkClassType((Class) actualType); + } else if (actualType instanceof WildcardType) { + for (final Type boundType : ((WildcardType) actualType).getUpperBounds()) { + if (boundType instanceof Class) { + checkClassType((Class) boundType); + } else { + throw Asserts.error("Unsupported wildcard bound type in %s(List<...>): %s", parent, boundType); + } + } + } else { + throw Asserts.error("Unsupported type argument type in %s(List<...>): %s", parent, actualType); + } + } + + @SuppressWarnings("ChainOfInstanceofChecks") + private void checkConstructor() { + try { + final Type argType = parent.getConstructor(List.class).getGenericParameterTypes()[0]; + if (argType instanceof ParameterizedType) { + checkParametrizedType((ParameterizedType) argType); + } else if (argType instanceof Class) { + throw Asserts.error("Raw List type in %s(List)", parent.getName()); + } else { + throw Asserts.error("Unsupported argument type in %s(List<...>): %s", parent.getName(), argType); + } + } catch (final NoSuchMethodException e) { + throw Asserts.error("Missing %s(List<...>) constructor: %s", parent.getName(), e); + } + } + } + + public static void main(final String... args) { + MarkupTest.main(args); + SELECTOR.main(args); + } +} diff --git a/java/markup/MarkupTest.java b/java/markup/MarkupTest.java new file mode 100644 index 0000000..9574b13 --- /dev/null +++ b/java/markup/MarkupTest.java @@ -0,0 +1,97 @@ +package markup; + +import base.Selector; +import base.TestCounter; + +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +/** + * @author Georgiy Korneev (kgeorgiy@kgeorgiy.info) + */ +public final class MarkupTest { + private static final Consumer MARKDOWN = MarkupTest.variant( + "Markdown", Map.of( + "&[", "", "&]", "", + "<", "", ">", "" + ) + ); + + private static final Consumer HTML = MarkupTest.variant( + "Html", Map.of( + "&[", "

                                                                                                                                                                                                                                                                                                                                                  ", "&]", "

                                                                                                                                                                                                                                                                                                                                                  ", + "*<", "", "*>", "", + "__<", "", "__>", "", + "~<", "", "~>", "" + ) + ); + + public static final Selector SELECTOR = new Selector(MarkupTest.class) + .variant("Base", MARKDOWN) + .variant("3637", MARKDOWN) + .variant("3839", MARKDOWN) + .variant("3435", HTML) + .variant("3233", HTML) + .variant("4142", MARKDOWN) + .variant("4749", MARKDOWN) + + ; + + public static Consumer variant(final String name, final Map mapping) { + return MarkupTester.variant(MarkupTest::test, name, mapping); + } + + private MarkupTest() { + } + + public static void test(final MarkupTester.Checker checker) { + test(checker, new Paragraph(List.of(new Text("Hello"))), "Hello"); + test(checker, new Paragraph(List.of(new Emphasis(List.of(new Text("Hello"))))), "*"); + test(checker, new Paragraph(List.of(new Strong(List.of(new Text("Hello"))))), "__"); + test(checker, new Paragraph(List.of(new Strikeout(List.of(new Text("Hello"))))), "~"); + + final Paragraph paragraph = new Paragraph(List.of( + new Strong(List.of( + new Text("1"), + new Strikeout(List.of( + new Text("2"), + new Emphasis(List.of( + new Text("3"), + new Text("4") + )), + new Text("5") + )), + new Text("6") + )) + )); + test(checker, paragraph, "__<1~<2*<34*>5~>6__>"); + test( + checker, + new Paragraph(List.of(new Strong(List.of( + new Text("sdq"), + new Strikeout(List.of(new Emphasis(List.of(new Text("r"))), new Text("vavc"))), + new Text("zg") + )))), + "__vavc~>zg__>" + ); + test( + checker, + new Paragraph(List.of(new Strikeout(List.of(new Strong(List.of(new Strikeout(List.of(new Text("е"), new Text("е"), new Text("г"))), new Text("ftje"), new Strong(List.of(new Text("йцць"), new Text("р"))))), new Strong(List.of(new Strikeout(List.of(new Text("д"), new Text("б"), new Text("е"))), new Strong(List.of(new Text("лъ"), new Text("шщ"))), new Strong(List.of(new Text("б"), new Text("еи"))))), new Emphasis(List.of(new Emphasis(List.of(new Text("м"), new Text("к"))), new Emphasis(List.of(new Text("уаужш"), new Text("ш"))), new Strong(List.of(new Text("рб"), new Text("щ"))))))), new Text("a"), new Strikeout(List.of(new Text("no"), new Text("ddw"), new Strong(List.of(new Emphasis(List.of(new Text("щ"), new Text("ча"))), new Emphasis(List.of(new Text("ъп"), new Text("ш"))), new Text("psk"))))))), + "~<__<~<еег~>ftje__<йццьр__>__>__<~<дбе~>__<лъшщ__>__<беи__>__>*<*<мк*>*<уаужшш*>__<рбщ__>*>~>a~*<ъпш*>psk__>~>" + ); + test( + checker, + new Paragraph(List.of(new Strikeout(List.of(new Strong(List.of(new Strikeout(List.of(new Emphasis(List.of(new Text("об"))), new Strikeout(List.of(new Text("ц"))), new Text("зснцйцць"), new Text("р"), new Text("а"))), new Strikeout(List.of(new Strikeout(List.of(new Text("б"))), new Strikeout(List.of(new Text("ялъ"))), new Text("шщ"), new Text("ф"), new Text("м"))), new Emphasis(List.of(new Emphasis(List.of(new Text("узр"))), new Text("i"), new Text("и"), new Text("г"), new Text("с"))), new Strong(List.of(new Strong(List.of(new Text("шмрб"))), new Strong(List.of(new Text("ь"))), new Text("з"), new Text("з"), new Text("фь"))), new Text("ddw"))), new Strong(List.of(new Emphasis(List.of(new Emphasis(List.of(new Text("ввтъп"))), new Strong(List.of(new Text("ш"))), new Text("хте"), new Text("чюе"), new Text("х"))), new Text("g"), new Strikeout(List.of(new Strikeout(List.of(new Text("ддзюцщ"))), new Strong(List.of(new Text("к"))), new Text("йщ"), new Text("э"), new Text("то"))), new Strong(List.of(new Emphasis(List.of(new Text("ж"))), new Text("t"), new Text("ыеж"), new Text("ч"), new Text("г"))), new Text("kwt"))), new Strong(List.of(new Strong(List.of(new Emphasis(List.of(new Text("ш"))), new Strong(List.of(new Text("х"))), new Text("уи"), new Text("о"), new Text("п"))), new Emphasis(List.of(new Text("zn"), new Strong(List.of(new Text("нш"))), new Text("диуьг"), new Text("вй"), new Text("шш"))), new Strong(List.of(new Emphasis(List.of(new Text("ьмша"))), new Emphasis(List.of(new Text("у"))), new Text("в"), new Text("у"), new Text("ир"))), new Emphasis(List.of(new Strikeout(List.of(new Text("я"))), new Strikeout(List.of(new Text("зоъ"))), new Text("эн"), new Text("ъ"), new Text("ьо"))), new Text("cqqzbhtn"))), new Text("i"), new Strong(List.of(new Text("i"), new Strikeout(List.of(new Strong(List.of(new Text("ш"))), new Strong(List.of(new Text("к"))), new Text("ж"), new Text("б"), new Text("ащ"))), new Strikeout(List.of(new Strikeout(List.of(new Text("пян"))), new Emphasis(List.of(new Text("ц"))), new Text("ю"), new Text("дрк"), new Text("лщ"))), new Strong(List.of(new Text("xywa"), new Text("ряэк"), new Text("п"), new Text("э"), new Text("т"))), new Strong(List.of(new Strikeout(List.of(new Text("е"))), new Text("чб"), new Text("зс"), new Text("екйюд"), new Text("чх"))))))), new Strikeout(List.of(new Strong(List.of(new Strong(List.of(new Strong(List.of(new Text("юи"))), new Emphasis(List.of(new Text("и"))), new Text("п"), new Text("вх"), new Text("ф"))), new Strong(List.of(new Strong(List.of(new Text("щюереф"))), new Text("otvic"), new Text("ж"), new Text("уыа"), new Text("ьскю"))), new Text("x"), new Strikeout(List.of(new Emphasis(List.of(new Text("ж"))), new Strikeout(List.of(new Text("зыйгг"))), new Text("о"), new Text("ш"), new Text("ф"))), new Text("zf"))), new Emphasis(List.of(new Text("a"), new Strikeout(List.of(new Emphasis(List.of(new Text("э"))), new Text("amqcfdzrg"), new Text("кыч"), new Text("к"), new Text("я"))), new Strikeout(List.of(new Strong(List.of(new Text("нфны"))), new Strikeout(List.of(new Text("гщ"))), new Text("ы"), new Text("шя"), new Text("е"))), new Strong(List.of(new Strong(List.of(new Text("ъю"))), new Emphasis(List.of(new Text("яхе"))), new Text("б"), new Text("бц"), new Text("еящж"))), new Text("cn"))), new Emphasis(List.of(new Strong(List.of(new Strong(List.of(new Text("л"))), new Text("wl"), new Text("оде"), new Text("кл"), new Text("еок"))), new Strikeout(List.of(new Strikeout(List.of(new Text("яяиь"))), new Strong(List.of(new Text("ик"))), new Text("юью"), new Text("ь"), new Text("э"))), new Emphasis(List.of(new Strikeout(List.of(new Text("жп"))), new Emphasis(List.of(new Text("ц"))), new Text("ндюз"), new Text("ч"), new Text("н"))), new Text("r"), new Strikeout(List.of(new Strikeout(List.of(new Text("зьуес"))), new Text("к"), new Text("и"), new Text("к"), new Text("й"))))), new Strikeout(List.of(new Emphasis(List.of(new Strikeout(List.of(new Text("еы"))), new Emphasis(List.of(new Text("б"))), new Text("сйсыр"), new Text("я"), new Text("т"))), new Emphasis(List.of(new Emphasis(List.of(new Text("з"))), new Strong(List.of(new Text("ахыы"))), new Text("х"), new Text("м"), new Text("п"))), new Strikeout(List.of(new Text("b"), new Text("o"), new Text("шьх"), new Text("йз"), new Text("ж"))), new Text("udlh"), new Strikeout(List.of(new Strikeout(List.of(new Text("п"))), new Text("хъфоз"), new Text("е"), new Text("ыф"), new Text("ю"))))), new Text("z"))), new Text("hy"), new Strong(List.of(new Text("tyv"), new Text("x"), new Strikeout(List.of(new Text("vzb"), new Strong(List.of(new Text("oi"), new Text("r"), new Text("ю"), new Text("с"), new Text("еппзмл"))), new Text("r"), new Emphasis(List.of(new Strikeout(List.of(new Text("игс"))), new Emphasis(List.of(new Text("нян"))), new Text("ю"), new Text("с"), new Text("цлъ"))), new Text("rptq"))), new Emphasis(List.of(new Strong(List.of(new Text("u"), new Strong(List.of(new Text("кще"))), new Text("пхте"), new Text("у"), new Text("з"))), new Text("zbmflu"), new Strikeout(List.of(new Strong(List.of(new Text("л"))), new Emphasis(List.of(new Text("ко"))), new Text("ъ"), new Text("щ"), new Text("жч"))), new Strong(List.of(new Strong(List.of(new Text("ж"))), new Strikeout(List.of(new Text("еъ"))), new Text("в"), new Text("ф"), new Text("йб"))), new Text("kvuf"))), new Strikeout(List.of(new Text("azn"), new Strikeout(List.of(new Strong(List.of(new Text("ъ"))), new Emphasis(List.of(new Text("ре"))), new Text("йч"), new Text("н"), new Text("ир"))), new Emphasis(List.of(new Emphasis(List.of(new Text("с"))), new Strong(List.of(new Text("щ"))), new Text("ъсбчиюзи"), new Text("сэ"), new Text("е"))), new Strikeout(List.of(new Emphasis(List.of(new Text("о"))), new Text("г"), new Text("бвщ"), new Text("пр"), new Text("йвъч"))), new Text("c"))))), new Strong(List.of(new Strikeout(List.of(new Strikeout(List.of(new Emphasis(List.of(new Text("жбфц"))), new Strong(List.of(new Text("рцч"))), new Text("хе"), new Text("ж"), new Text("ы"))), new Strikeout(List.of(new Emphasis(List.of(new Text("я"))), new Emphasis(List.of(new Text("мн"))), new Text("яе"), new Text("е"), new Text("дхпг"))), new Emphasis(List.of(new Emphasis(List.of(new Text("нй"))), new Text("gf"), new Text("и"), new Text("хю"), new Text("ця"))), new Strong(List.of(new Emphasis(List.of(new Text("о"))), new Emphasis(List.of(new Text("ъ"))), new Text("лм"), new Text("ы"), new Text("рц"))), new Emphasis(List.of(new Strikeout(List.of(new Text("я"))), new Text("ыл"), new Text("г"), new Text("я"), new Text("эй"))))), new Text("qi"), new Emphasis(List.of(new Text("dtt"), new Emphasis(List.of(new Strong(List.of(new Text("пв"))), new Text("i"), new Text("яешц"), new Text("йшй"), new Text("щмив"))), new Text("u"), new Text("d"), new Strikeout(List.of(new Strikeout(List.of(new Text("о"))), new Text("иов"), new Text("к"), new Text("кои"), new Text("яс"))))), new Strikeout(List.of(new Emphasis(List.of(new Text("j"), new Strong(List.of(new Text("эя"))), new Text("шыф"), new Text("дрн"), new Text("щ"))), new Text("j"), new Strong(List.of(new Emphasis(List.of(new Text("ю"))), new Strikeout(List.of(new Text("чцин"))), new Text("сф"), new Text("з"), new Text("юэи"))), new Emphasis(List.of(new Emphasis(List.of(new Text("цс"))), new Text("ювус"), new Text("ъ"), new Text("щэны"), new Text("б"))), new Emphasis(List.of(new Text("cbogf"), new Text("э"), new Text("ж"), new Text("ш"), new Text("м"))))), new Strikeout(List.of(new Strong(List.of(new Strong(List.of(new Text("ф"))), new Text("w"), new Text("цеъ"), new Text("н"), new Text("ем"))), new Strikeout(List.of(new Strikeout(List.of(new Text("л"))), new Strong(List.of(new Text("э"))), new Text("лд"), new Text("эд"), new Text("л"))), new Emphasis(List.of(new Emphasis(List.of(new Text("уг"))), new Strikeout(List.of(new Text("зп"))), new Text("юб"), new Text("сгы"), new Text("шю"))), new Strikeout(List.of(new Emphasis(List.of(new Text("рйей"))), new Text("с"), new Text("зюй"), new Text("р"), new Text("в"))), new Emphasis(List.of(new Text("p"), new Text("у"), new Text("на"), new Text("б"), new Text("х"))))))))), + "~<__<~<*<об*>~<ц~>зснцйццьра~>~<~<б~>~<ялъ~>шщфм~>*<*<узр*>iигс*>__<__<шмрб__>__<ь__>ззфь__>ddw__>__<*<*<ввтъп*>__<ш__>хтечюех*>g~<~<ддзюцщ~>__<к__>йщэто~>__<*<ж*>tыежчг__>kwt__>__<__<*<ш*>__<х__>уиоп__>*диуьгвйшш*>__<*<ьмша*>*<у*>вуир__>*<~<я~>~<зоъ~>энъьо*>cqqzbhtn__>i____<к__>жбащ~>~<~<пян~>*<ц*>юдрклщ~>____<~<е~>чбзсекйюдчх__>__>~>~<__<__<__<юи__>*<и*>пвхф__>__<__<щюереф__>otvicжуыаьскю__>x~<*<ж*>~<зыйгг~>ошф~>zf__>*amqcfdzrgкычкя~>~<__<нфны__>~<гщ~>ышяе~>__<__<ъю__>*<яхе*>ббцеящж__>cn*>*<__<__<л__>wlодеклеок__>~<~<яяиь~>__<ик__>юьюьэ~>*<~<жп~>*<ц*>ндюзчн*>r~<~<зьуес~>кикй~>*>~<*<~<еы~>*<б*>сйсырят*>*<*<з*>__<ахыы__>хмп*>~udlh~<~<п~>хъфозеыфю~>~>z~>hy__r*<~<игс~>*<нян*>юсцлъ*>rptq~>*<__пхтеуз__>zbmflu~<__<л__>*<ко*>ъщжч~>__<__<ж__>~<еъ~>вфйб__>kvuf*>~*<ре*>йчнир~>*<*<с*>__<щ__>ъсбчиюзисэе*>~<*<о*>гбвщпрйвъч~>c~>__>__<~<~<*<жбфц*>__<рцч__>хежы~>~<*<я*>*<мн*>яеедхпг~>*<*<нй*>gfихюця*>__<*<о*>*<ъ*>лмырц__>*<~<я~>ылгяэй*>~>qi*iяешцйшйщмив*>ud~<~<о~>иовккоияс~>*>~<*шыфдрнщ*>j__<*<ю*>~<чцин~>сфзюэи__>*<*<цс*>ювусъщэныб*>*~>~<__<__<ф__>wцеънем__>~<~<л~>__<э__>лдэдл~>*<*<уг*>~<зп~>юбсгышю*>~<*<рйей*>сзюйрв~>*~>__>" + ); + } + + private static void test(final MarkupTester.Checker checker, final Paragraph paragraph, final String template) { + checker.test(paragraph, String.format("&[%s&]", template)); + } + + public static void main(final String... args) { + SELECTOR.main(args); + } +} diff --git a/java/markup/MarkupTester.java b/java/markup/MarkupTester.java new file mode 100644 index 0000000..4d93d97 --- /dev/null +++ b/java/markup/MarkupTester.java @@ -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 mapping; + private final String toString; + + private MarkupTester(final Map mapping, final String toString) { + this.mapping = mapping; + this.toString = toString; + } + + public static Consumer variant(final Consumer checker, final String name, final Map mapping) { + return counter -> test(checker).accept(new MarkupTester(mapping, "to" + name), counter); + } + + public static BiConsumer test(final Consumer 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 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 void test(final T value, String expectedTemplate) { + final MethodHandle method = findMethod(value); + for (final Map.Entry 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()); + }); + } + } +} diff --git a/java/markup/OrderedList.java b/java/markup/OrderedList.java new file mode 100644 index 0000000..1c170ca --- /dev/null +++ b/java/markup/OrderedList.java @@ -0,0 +1,13 @@ +package markup; + +import java.util.List; + +/** + * @author Nikita Doschennikov (me@fymio.us) + */ +public class OrderedList extends AbstractList { + + public OrderedList(List items) { + super(items, "ol", "\\begin{enumerate}", "\\end{enumerate}"); + } +} diff --git a/java/markup/Paragraph.java b/java/markup/Paragraph.java new file mode 100644 index 0000000..f5a0d33 --- /dev/null +++ b/java/markup/Paragraph.java @@ -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 items) { + super(items, "", "p", "\\par{}", ""); + } +} diff --git a/java/markup/PartOfParagraph.java b/java/markup/PartOfParagraph.java new file mode 100644 index 0000000..fdcbfa0 --- /dev/null +++ b/java/markup/PartOfParagraph.java @@ -0,0 +1,6 @@ +package markup; + +/** + * @author Nikita Doschennikov (me@fymio.us) + */ +public interface PartOfParagraph extends Markup {} diff --git a/java/markup/PrimePart.java b/java/markup/PrimePart.java new file mode 100644 index 0000000..943bbe6 --- /dev/null +++ b/java/markup/PrimePart.java @@ -0,0 +1,6 @@ +package markup; + +/** + * @author Nikita Doschennikov (me@fymio.us) + */ +public interface PrimePart extends Markup {} diff --git a/java/markup/Strikeout.java b/java/markup/Strikeout.java new file mode 100644 index 0000000..7cf9308 --- /dev/null +++ b/java/markup/Strikeout.java @@ -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 items) { + super(items, "~", "s", "\\textst{", "}"); + } +} diff --git a/java/markup/Strong.java b/java/markup/Strong.java new file mode 100644 index 0000000..a5e620b --- /dev/null +++ b/java/markup/Strong.java @@ -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 items) { + super(items, "__", "strong", "\\textbf{", "}"); + } +} diff --git a/java/markup/Tex.java b/java/markup/Tex.java new file mode 100644 index 0000000..4b827e6 --- /dev/null +++ b/java/markup/Tex.java @@ -0,0 +1,8 @@ +package markup; + +/** + * @author Nikita Doschennikov (me@fymio.us) + */ +public interface Tex { + void toTex(StringBuilder sb); +} diff --git a/java/markup/Text.java b/java/markup/Text.java new file mode 100644 index 0000000..34b67e9 --- /dev/null +++ b/java/markup/Text.java @@ -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); + } +} diff --git a/java/markup/UnorderedList.java b/java/markup/UnorderedList.java new file mode 100644 index 0000000..b8dfa03 --- /dev/null +++ b/java/markup/UnorderedList.java @@ -0,0 +1,13 @@ +package markup; + +import java.util.List; + +/** + * @author Nikita Doschennikov (me@fymio.us) + */ +public class UnorderedList extends AbstractList { + + public UnorderedList(List items) { + super(items, "ul", "\\begin{itemize}", "\\end{itemize}"); + } +} diff --git a/java/markup/assets/diagram.png b/java/markup/assets/diagram.png new file mode 100644 index 0000000..e46d819 Binary files /dev/null and b/java/markup/assets/diagram.png differ diff --git a/java/markup/assets/diagram.svg b/java/markup/assets/diagram.svg new file mode 100644 index 0000000..43cae7d --- /dev/null +++ b/java/markup/assets/diagram.svg @@ -0,0 +1 @@ +

                                                                                                                                                                                                                                                                                                                                                  contains

                                                                                                                                                                                                                                                                                                                                                  contains

                                                                                                                                                                                                                                                                                                                                                  contains

                                                                                                                                                                                                                                                                                                                                                  contains

                                                                                                                                                                                                                                                                                                                                                  contains

                                                                                                                                                                                                                                                                                                                                                  contains

                                                                                                                                                                                                                                                                                                                                                  contains

                                                                                                                                                                                                                                                                                                                                                  «interface»

                                                                                                                                                                                                                                                                                                                                                  Markdown

                                                                                                                                                                                                                                                                                                                                                  +toMarkdown(StringBuilder)

                                                                                                                                                                                                                                                                                                                                                  «interface»

                                                                                                                                                                                                                                                                                                                                                  Html

                                                                                                                                                                                                                                                                                                                                                  +toHtml(StringBuilder)

                                                                                                                                                                                                                                                                                                                                                  «interface»

                                                                                                                                                                                                                                                                                                                                                  Markup

                                                                                                                                                                                                                                                                                                                                                  +toMarkdown(StringBuilder)

                                                                                                                                                                                                                                                                                                                                                  +toHtml(StringBuilder)

                                                                                                                                                                                                                                                                                                                                                  «interface»

                                                                                                                                                                                                                                                                                                                                                  PartOfParagraph

                                                                                                                                                                                                                                                                                                                                                  +toMarkdown(StringBuilder)

                                                                                                                                                                                                                                                                                                                                                  +toHtml(StringBuilder)

                                                                                                                                                                                                                                                                                                                                                  «interface»

                                                                                                                                                                                                                                                                                                                                                  ContainsInListItem

                                                                                                                                                                                                                                                                                                                                                  +toMarkdown(StringBuilder)

                                                                                                                                                                                                                                                                                                                                                  +toHtml(StringBuilder)

                                                                                                                                                                                                                                                                                                                                                  «interface»

                                                                                                                                                                                                                                                                                                                                                  PrimePart

                                                                                                                                                                                                                                                                                                                                                  +toMarkdown(StringBuilder)

                                                                                                                                                                                                                                                                                                                                                  +toHtml(StringBuilder)

                                                                                                                                                                                                                                                                                                                                                  «abstract»

                                                                                                                                                                                                                                                                                                                                                  AbstractMarkup

                                                                                                                                                                                                                                                                                                                                                  #List<Markup> items

                                                                                                                                                                                                                                                                                                                                                  -String highlightMarkdown

                                                                                                                                                                                                                                                                                                                                                  -String highlightHtml

                                                                                                                                                                                                                                                                                                                                                  -String highlightTexOpen

                                                                                                                                                                                                                                                                                                                                                  -String highlightTexClose

                                                                                                                                                                                                                                                                                                                                                  +AbstractMarkup(List, String, String, String, String)

                                                                                                                                                                                                                                                                                                                                                  +toMarkdown(StringBuilder)

                                                                                                                                                                                                                                                                                                                                                  +toHtml(StringBuilder)

                                                                                                                                                                                                                                                                                                                                                  +toTex(StringBuilder)

                                                                                                                                                                                                                                                                                                                                                  «abstract»

                                                                                                                                                                                                                                                                                                                                                  AbstractList

                                                                                                                                                                                                                                                                                                                                                  -List<ListItem> items

                                                                                                                                                                                                                                                                                                                                                  -String highlight

                                                                                                                                                                                                                                                                                                                                                  -String texBegin

                                                                                                                                                                                                                                                                                                                                                  -String texEnd

                                                                                                                                                                                                                                                                                                                                                  +AbstractList(List, String, String, String)

                                                                                                                                                                                                                                                                                                                                                  +toHtml(StringBuilder)

                                                                                                                                                                                                                                                                                                                                                  +toMarkdown(StringBuilder)

                                                                                                                                                                                                                                                                                                                                                  +toTex(StringBuilder)

                                                                                                                                                                                                                                                                                                                                                  Text

                                                                                                                                                                                                                                                                                                                                                  -String text

                                                                                                                                                                                                                                                                                                                                                  +Text(String)

                                                                                                                                                                                                                                                                                                                                                  +toMarkdown(StringBuilder)

                                                                                                                                                                                                                                                                                                                                                  +toHtml(StringBuilder)

                                                                                                                                                                                                                                                                                                                                                  +toTex(StringBuilder)

                                                                                                                                                                                                                                                                                                                                                  Emphasis

                                                                                                                                                                                                                                                                                                                                                  +Emphasis(List<PartOfParagraph>)

                                                                                                                                                                                                                                                                                                                                                  Strong

                                                                                                                                                                                                                                                                                                                                                  +Strong(List<PartOfParagraph>)

                                                                                                                                                                                                                                                                                                                                                  Strikeout

                                                                                                                                                                                                                                                                                                                                                  +Strikeout(List<PartOfParagraph>)

                                                                                                                                                                                                                                                                                                                                                  Paragraph

                                                                                                                                                                                                                                                                                                                                                  +Paragraph(List<PartOfParagraph>)

                                                                                                                                                                                                                                                                                                                                                  OrderedList

                                                                                                                                                                                                                                                                                                                                                  +OrderedList(List<ListItem>)

                                                                                                                                                                                                                                                                                                                                                  UnorderedList

                                                                                                                                                                                                                                                                                                                                                  +UnorderedList(List<ListItem>)

                                                                                                                                                                                                                                                                                                                                                  ListItem

                                                                                                                                                                                                                                                                                                                                                  +ListItem(List<ContainsInListItem>)

                                                                                                                                                                                                                                                                                                                                                  \ No newline at end of file diff --git a/java/markup/assets/mermaid/diagram.mmd b/java/markup/assets/mermaid/diagram.mmd new file mode 100644 index 0000000..18bba88 --- /dev/null +++ b/java/markup/assets/mermaid/diagram.mmd @@ -0,0 +1,157 @@ +classDiagram + %% Интерфейсы + class Markdown { + <> + +toMarkdown(StringBuilder) + } + + class Html { + <> + +toHtml(StringBuilder) + } + + class Markup { + <> + +toMarkdown(StringBuilder) + +toHtml(StringBuilder) + } + + class PartOfParagraph { + <> + +toMarkdown(StringBuilder) + +toHtml(StringBuilder) + } + + class ContainsInListItem { + <> + +toMarkdown(StringBuilder) + +toHtml(StringBuilder) + } + + class PrimePart { + <> + +toMarkdown(StringBuilder) + +toHtml(StringBuilder) + } + + %% Наследование интерфейсов + Markup --|> Markdown + Markup --|> Html + PartOfParagraph --|> Markup + ContainsInListItem --|> Markup + PrimePart --|> Markup + + %% Абстрактные классы + class AbstractMarkup { + <> + #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 { + <> + -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 diff --git a/java/markup/package-info.java b/java/markup/package-info.java new file mode 100644 index 0000000..fdef1bc --- /dev/null +++ b/java/markup/package-info.java @@ -0,0 +1,7 @@ +/** + * Tests for Markup homework + * of Introduction to Programming course. + * + * @author Georgiy Korneev (kgeorgiy@kgeorgiy.info) + */ +package markup; \ No newline at end of file diff --git a/java/md2html/BlockCreator.java b/java/md2html/BlockCreator.java new file mode 100644 index 0000000..6cdfb30 --- /dev/null +++ b/java/md2html/BlockCreator.java @@ -0,0 +1,61 @@ +package md2html; + +import java.util.ArrayList; +import java.util.List; + +public class BlockCreator { + private final String text; + private final List blocks; + + public BlockCreator(String text) { + this.text = text; + blocks = new ArrayList<>(); + } + + public List 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'); + } +} diff --git a/java/md2html/Code.java b/java/md2html/Code.java new file mode 100644 index 0000000..7fac999 --- /dev/null +++ b/java/md2html/Code.java @@ -0,0 +1,11 @@ +package md2html; + +import java.util.List; +import markup.*; + +public class Code extends AbstractMarkup implements PartOfParagraph { + + public Code(List items) { + super(items, "'", "code", "", ""); + } +} diff --git a/java/md2html/Header.java b/java/md2html/Header.java new file mode 100644 index 0000000..9558c4f --- /dev/null +++ b/java/md2html/Header.java @@ -0,0 +1,14 @@ +package md2html; + +import java.util.List; +import markup.*; + +public class Header extends AbstractMarkup implements PrimePart { + public Header(List items, int level) { + super(items, + "#".repeat(level), + "h" + level, + "", + ""); + } +} \ No newline at end of file diff --git a/java/md2html/Md2Html.java b/java/md2html/Md2Html.java new file mode 100644 index 0000000..eec8c13 --- /dev/null +++ b/java/md2html/Md2Html.java @@ -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 blocks = creator.divideByBlocks(); + List 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()); + } + } +} diff --git a/java/md2html/Md2HtmlTest.java b/java/md2html/Md2HtmlTest.java new file mode 100644 index 0000000..78b186f --- /dev/null +++ b/java/md2html/Md2HtmlTest.java @@ -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 INS = tester -> tester + .test("<<вставка>>", "

                                                                                                                                                                                                                                                                                                                                                  вставка

                                                                                                                                                                                                                                                                                                                                                  ") + .test("Это <<вставка>>, вложенная в текст", "

                                                                                                                                                                                                                                                                                                                                                  Это вставка, вложенная в текст

                                                                                                                                                                                                                                                                                                                                                  ") + .spoiled("Это не <<вставка>>", "

                                                                                                                                                                                                                                                                                                                                                  Это не <<вставка>>

                                                                                                                                                                                                                                                                                                                                                  ", "<", ">") + .spoiled("Это не <<вставка>> 2", "

                                                                                                                                                                                                                                                                                                                                                  Это не <<вставка>> 2

                                                                                                                                                                                                                                                                                                                                                  ", "<", ">") + .addElement("ins", "<<", ">>"); + private static final Consumer DEL = tester -> tester + .test("}}удаление{{", "

                                                                                                                                                                                                                                                                                                                                                  удаление

                                                                                                                                                                                                                                                                                                                                                  ") + .test("Это }}удаление{{, вложенное в текст", "

                                                                                                                                                                                                                                                                                                                                                  Это удаление, вложенное в текст

                                                                                                                                                                                                                                                                                                                                                  ") + .spoiled("Это не }}удаление{{", "

                                                                                                                                                                                                                                                                                                                                                  Это не }}удаление{{

                                                                                                                                                                                                                                                                                                                                                  ", "{") + .spoiled("Это не }}удаление{{ 2", "

                                                                                                                                                                                                                                                                                                                                                  Это не }}удаление{{ 2

                                                                                                                                                                                                                                                                                                                                                  ", "{") + .addElement("del", "}}", "{{"); + + // === 3839 + private static final Consumer PRE = tester -> tester + .test("```код __без__ форматирования```", "

                                                                                                                                                                                                                                                                                                                                                  код __без__ форматирования

                                                                                                                                                                                                                                                                                                                                                  ") + .test( + "Это не `\\``код __без__ форматирования``\\`", + "

                                                                                                                                                                                                                                                                                                                                                  Это не `код без форматирования`

                                                                                                                                                                                                                                                                                                                                                  " + ) + .addElement("pre", "```", (checker, markup, input, output) -> { + final String contentS = checker.generateInput(markup).replace("`", ""); + + input.append("```").append(contentS).append("```"); + output.append("
                                                                                                                                                                                                                                                                                                                                                  ").append(contentS.replace("<", "<").replace(">", "")).append("
                                                                                                                                                                                                                                                                                                                                                  "); + }); + + // === 3435 + private static final Consumer SAMP = tester -> tester + .test("!!пример!!", "

                                                                                                                                                                                                                                                                                                                                                  пример

                                                                                                                                                                                                                                                                                                                                                  ") + .test("Это !!пример!!, вложенный в текст", "

                                                                                                                                                                                                                                                                                                                                                  Это пример, вложенный в текст

                                                                                                                                                                                                                                                                                                                                                  ") + .spoiled("Это не !!пример!!", "

                                                                                                                                                                                                                                                                                                                                                  Это не !!пример!!

                                                                                                                                                                                                                                                                                                                                                  ", "!") + .spoiled("Это не !!пример!! 2", "

                                                                                                                                                                                                                                                                                                                                                  Это не !!пример!! 2

                                                                                                                                                                                                                                                                                                                                                  ", "!") + .addElement("samp", "!!"); + + // === 3233 + private static final Consumer VAR = tester -> tester + .test("%переменная%", "

                                                                                                                                                                                                                                                                                                                                                  переменная

                                                                                                                                                                                                                                                                                                                                                  ") + .test("Это %переменная%, вложенная в текст", "

                                                                                                                                                                                                                                                                                                                                                  Это переменная, вложенная в текст

                                                                                                                                                                                                                                                                                                                                                  ") + .spoiled("Это не %переменная%", "

                                                                                                                                                                                                                                                                                                                                                  Это не %переменная%

                                                                                                                                                                                                                                                                                                                                                  ", "%") + .spoiled("Это не %переменная% 2", "

                                                                                                                                                                                                                                                                                                                                                  Это не %переменная% 2

                                                                                                                                                                                                                                                                                                                                                  ", "%") + .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); + } +} diff --git a/java/md2html/Md2HtmlTester.java b/java/md2html/Md2HtmlTester.java new file mode 100644 index 0000000..9538e81 --- /dev/null +++ b/java/md2html/Md2HtmlTester.java @@ -0,0 +1,355 @@ +package md2html; + +import base.*; + +import java.util.*; + +/** + * @author Georgiy Korneev (kgeorgiy@kgeorgiy.info) + */ +public class Md2HtmlTester { + private static final Map ESCAPES = Map.of("<", "<", ">", ">"); + + private final Map elements = new HashMap<>(); + private final Map> tags = new LinkedHashMap<>(); + private final List> tests = new ArrayList<>(); + + public Md2HtmlTester() { + addElement("em", "*"); + addElement("em", "_"); + addElement("strong", "**"); + addElement("strong", "__"); + addElement("s", "--"); + addElement("code", "`"); + + test( + "# Заголовок первого уровня\n\n", + "

                                                                                                                                                                                                                                                                                                                                                  Заголовок первого уровня

                                                                                                                                                                                                                                                                                                                                                  " + ); + test( + "## Второго\n\n", + "

                                                                                                                                                                                                                                                                                                                                                  Второго

                                                                                                                                                                                                                                                                                                                                                  " + ); + test( + "### Третьего ## уровня\n\n", + "

                                                                                                                                                                                                                                                                                                                                                  Третьего ## уровня

                                                                                                                                                                                                                                                                                                                                                  " + ); + test( + "#### Четвертого\n# Все еще четвертого\n\n", + "

                                                                                                                                                                                                                                                                                                                                                  Четвертого\n# Все еще четвертого

                                                                                                                                                                                                                                                                                                                                                  " + ); + test( + "Этот абзац текста\nсодержит две строки.", + "

                                                                                                                                                                                                                                                                                                                                                  Этот абзац текста\nсодержит две строки.

                                                                                                                                                                                                                                                                                                                                                  " + ); + test( + " # Может показаться, что это заголовок.\nНо нет, это абзац, начинающийся с `#`.\n\n", + "

                                                                                                                                                                                                                                                                                                                                                  # Может показаться, что это заголовок.\nНо нет, это абзац, начинающийся с #.

                                                                                                                                                                                                                                                                                                                                                  " + ); + test( + "#И это не заголовок.\n\n", + "

                                                                                                                                                                                                                                                                                                                                                  #И это не заголовок.

                                                                                                                                                                                                                                                                                                                                                  " + ); + test( + "###### Заголовки могут быть многострочными\n(и с пропуском заголовков предыдущих уровней)\n\n", + "
                                                                                                                                                                                                                                                                                                                                                  Заголовки могут быть многострочными\n(и с пропуском заголовков предыдущих уровней)
                                                                                                                                                                                                                                                                                                                                                  " + ); + test( + "Мы все любим *выделять* текст _разными_ способами.\n**Сильное выделение**, используется гораздо реже,\nно __почему бы и нет__?\nНемного --зачеркивания-- еще никому не вредило.\nКод представляется элементом `code`.\n\n", + "

                                                                                                                                                                                                                                                                                                                                                  Мы все любим выделять текст разными способами.\nСильное выделение, используется гораздо реже,\nно почему бы и нет?\nНемного зачеркивания еще никому не вредило.\nКод представляется элементом code.

                                                                                                                                                                                                                                                                                                                                                  " + ); + test( + "Обратите внимание, как экранируются специальные\nHTML-символы, такие как `<`, `>` и `&`.\n\n", + "

                                                                                                                                                                                                                                                                                                                                                  Обратите внимание, как экранируются специальные\nHTML-символы, такие как <, > и &.

                                                                                                                                                                                                                                                                                                                                                  " + ); + test( + "Экранирование должно работать во всех местах: <>&.\n\n", + "

                                                                                                                                                                                                                                                                                                                                                  Экранирование должно работать во всех местах: <>&.

                                                                                                                                                                                                                                                                                                                                                  " + ); + test( + "Знаете ли вы, что в Markdown, одиночные * и _\nне означают выделение?\nОни так же могут быть заэкранированы\nпри помощи обратного слэша: \\*.", + "

                                                                                                                                                                                                                                                                                                                                                  Знаете ли вы, что в Markdown, одиночные * и _\nне означают выделение?\nОни так же могут быть заэкранированы\nпри помощи обратного слэша: *.

                                                                                                                                                                                                                                                                                                                                                  " + ); + test( + "\n\n\nЛишние пустые строки должны игнорироваться.\n\n\n\n", + "

                                                                                                                                                                                                                                                                                                                                                  Лишние пустые строки должны игнорироваться.

                                                                                                                                                                                                                                                                                                                                                  " + ); + test( + "Любите ли вы *вложенные __выделения__* так,\nкак __--люблю--__ их я?", + "

                                                                                                                                                                                                                                                                                                                                                  Любите ли вы вложенные выделения так,\nкак люблю их я?

                                                                                                                                                                                                                                                                                                                                                  " + ); + + test( + """ + # Заголовок первого уровня + + ## Второго + + ### Третьего ## уровня + + #### Четвертого + # Все еще четвертого + + Этот абзац текста + содержит две строки. + + # Может показаться, что это заголовок. + Но нет, это абзац, начинающийся с `#`. + + #И это не заголовок. + + ###### Заголовки могут быть многострочными + (и с пропуском заголовков предыдущих уровней) + + Мы все любим *выделять* текст _разными_ способами. + **Сильное выделение**, используется гораздо реже, + но __почему бы и нет__? + Немного --зачеркивания-- еще никому не вредило. + Код представляется элементом `code`. + + Обратите внимание, как экранируются специальные + HTML-символы, такие как `<`, `>` и `&`. + + Знаете ли вы, что в Markdown, одиночные * и _ + не означают выделение? + Они так же могут быть заэкранированы + при помощи обратного слэша: \\*. + + + + Лишние пустые строки должны игнорироваться. + + Любите ли вы *вложенные __выделения__* так, + как __--люблю--__ их я? + """, + """ +

                                                                                                                                                                                                                                                                                                                                                  Заголовок первого уровня

                                                                                                                                                                                                                                                                                                                                                  +

                                                                                                                                                                                                                                                                                                                                                  Второго

                                                                                                                                                                                                                                                                                                                                                  +

                                                                                                                                                                                                                                                                                                                                                  Третьего ## уровня

                                                                                                                                                                                                                                                                                                                                                  +

                                                                                                                                                                                                                                                                                                                                                  Четвертого + # Все еще четвертого

                                                                                                                                                                                                                                                                                                                                                  +

                                                                                                                                                                                                                                                                                                                                                  Этот абзац текста + содержит две строки.

                                                                                                                                                                                                                                                                                                                                                  +

                                                                                                                                                                                                                                                                                                                                                  # Может показаться, что это заголовок. + Но нет, это абзац, начинающийся с #.

                                                                                                                                                                                                                                                                                                                                                  +

                                                                                                                                                                                                                                                                                                                                                  #И это не заголовок.

                                                                                                                                                                                                                                                                                                                                                  +
                                                                                                                                                                                                                                                                                                                                                  Заголовки могут быть многострочными + (и с пропуском заголовков предыдущих уровней)
                                                                                                                                                                                                                                                                                                                                                  +

                                                                                                                                                                                                                                                                                                                                                  Мы все любим выделять текст разными способами. + Сильное выделение, используется гораздо реже, + но почему бы и нет? + Немного зачеркивания еще никому не вредило. + Код представляется элементом code.

                                                                                                                                                                                                                                                                                                                                                  +

                                                                                                                                                                                                                                                                                                                                                  Обратите внимание, как экранируются специальные + HTML-символы, такие как <, > и &.

                                                                                                                                                                                                                                                                                                                                                  +

                                                                                                                                                                                                                                                                                                                                                  Знаете ли вы, что в Markdown, одиночные * и _ + не означают выделение? + Они так же могут быть заэкранированы + при помощи обратного слэша: *.

                                                                                                                                                                                                                                                                                                                                                  +

                                                                                                                                                                                                                                                                                                                                                  Лишние пустые строки должны игнорироваться.

                                                                                                                                                                                                                                                                                                                                                  +

                                                                                                                                                                                                                                                                                                                                                  Любите ли вы вложенные выделения так, + как люблю их я?

                                                                                                                                                                                                                                                                                                                                                  + """ + ); + + test("# Без перевода строки в конце", "

                                                                                                                                                                                                                                                                                                                                                  Без перевода строки в конце

                                                                                                                                                                                                                                                                                                                                                  "); + test("# Один перевод строки в конце\n", "

                                                                                                                                                                                                                                                                                                                                                  Один перевод строки в конце

                                                                                                                                                                                                                                                                                                                                                  "); + test("# Два перевода строки в конце\n\n", "

                                                                                                                                                                                                                                                                                                                                                  Два перевода строки в конце

                                                                                                                                                                                                                                                                                                                                                  "); + test( + "Выделение может *начинаться на одной строке,\n а заканчиваться* на другой", + "

                                                                                                                                                                                                                                                                                                                                                  Выделение может начинаться на одной строке,\n а заканчиваться на другой

                                                                                                                                                                                                                                                                                                                                                  " + ); + test("# *Выделение* и `код` в заголовках", "

                                                                                                                                                                                                                                                                                                                                                  Выделение и код в заголовках

                                                                                                                                                                                                                                                                                                                                                  "); + } + + 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(""); + } + + 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 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 test) { + runner.testEquals(counter, Arrays.asList(test.first().split("\n")), Arrays.asList(test.second().split("\n"))); + } + + private List randomMarkup() { + return Functional.map(tags.values(), random()::randomItem); + } + + private void randomTest(final int paragraphs, final int length, final List markup) { + final StringBuilder input = new StringBuilder(); + final StringBuilder output = new StringBuilder(); + emptyLines(input); + final List 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 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 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 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 markup, StringBuilder input, StringBuilder output); + } +} diff --git a/java/md2html/PrimePartCreator.java b/java/md2html/PrimePartCreator.java new file mode 100644 index 0000000..12f0d00 --- /dev/null +++ b/java/md2html/PrimePartCreator.java @@ -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 buildPart(MarkdownToken currentToken) { + List 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("<"); + } else if (block.charAt(currentChar) == '>') { + sb.append(">"); + } else if (block.charAt(currentChar) == '&') { + sb.append("&"); + } 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 items, + StringBuilder sb + ) { + if (!sb.isEmpty()) { + items.add(new Text(sb.toString())); + sb.setLength(0); + } + } +} diff --git a/java/md2html/package-info.java b/java/md2html/package-info.java new file mode 100644 index 0000000..05f3608 --- /dev/null +++ b/java/md2html/package-info.java @@ -0,0 +1,8 @@ +/** + * Tests for Markdown to HTML homework + * of Introduction to Programming course. + * + * + * @author Georgiy Korneev (kgeorgiy@kgeorgiy.info) + */ +package md2html; \ No newline at end of file diff --git a/java/reverse/FastReverseTest.java b/java/reverse/FastReverseTest.java new file mode 100644 index 0000000..1695d36 --- /dev/null +++ b/java/reverse/FastReverseTest.java @@ -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> OCT = Named.of("", + (r, i) -> r.nextBoolean() ? Integer.toString(i) : Integer.toOctalString(i) + (r.nextBoolean() ? "o" : "O") + ); + private static final Named> DEC = Named.of("", (r, i) -> Integer.toString(i)); + + private static final Named 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 MIN_C = Named.of("MinC", scan2((a, b) -> b)); + public static final Named 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); + } +} diff --git a/java/reverse/FastScanner.java b/java/reverse/FastScanner.java new file mode 100644 index 0000000..45b5101 --- /dev/null +++ b/java/reverse/FastScanner.java @@ -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(); + } +} \ No newline at end of file diff --git a/java/reverse/Reverse.java b/java/reverse/Reverse.java new file mode 100644 index 0000000..d8b4c25 --- /dev/null +++ b/java/reverse/Reverse.java @@ -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(); + } +} \ No newline at end of file diff --git a/java/reverse/ReverseAvg.java b/java/reverse/ReverseAvg.java new file mode 100644 index 0000000..8312e45 --- /dev/null +++ b/java/reverse/ReverseAvg.java @@ -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(); + } +} diff --git a/java/reverse/ReverseEven.java b/java/reverse/ReverseEven.java new file mode 100644 index 0000000..72c8ca4 --- /dev/null +++ b/java/reverse/ReverseEven.java @@ -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(); + } +} diff --git a/java/reverse/ReverseMax.java b/java/reverse/ReverseMax.java new file mode 100644 index 0000000..1e7756f --- /dev/null +++ b/java/reverse/ReverseMax.java @@ -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; + } +} diff --git a/java/reverse/ReverseMaxC.java b/java/reverse/ReverseMaxC.java new file mode 100644 index 0000000..e881cc1 --- /dev/null +++ b/java/reverse/ReverseMaxC.java @@ -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; + } +} diff --git a/java/reverse/ReverseRotate.java b/java/reverse/ReverseRotate.java new file mode 100644 index 0000000..3da4fd7 --- /dev/null +++ b/java/reverse/ReverseRotate.java @@ -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(); + } +} diff --git a/java/reverse/ReverseSum.java b/java/reverse/ReverseSum.java new file mode 100644 index 0000000..1880036 --- /dev/null +++ b/java/reverse/ReverseSum.java @@ -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(); + } +} diff --git a/java/reverse/ReverseTest.java b/java/reverse/ReverseTest.java new file mode 100644 index 0000000..de1bced --- /dev/null +++ b/java/reverse/ReverseTest.java @@ -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 REVERSE = Named.of("", ReverseTester::transform); + + + // === Max + + public static final Named MAX_C = Named.of("MaxC", scan2((a, b) -> b)); + public static final Named MAX = Named.of("Max", scan2(Math::max)); + + private static Op scan2(final LongBinaryOperator reduce) { + return ints -> { + // This code is intentionally obscure + final int length = Arrays.stream(ints).mapToInt(r -> r.length).max().orElse(0); + final long[] cs = new long[length]; + final long[] cc = new long[length + 1]; + Arrays.fill(cs, Integer.MIN_VALUE); + Arrays.fill(cc, Integer.MIN_VALUE); + //noinspection NestedAssignment + final long[][] rows = range(ints.length).mapToObj(i -> { + range(ints[i].length).forEachOrdered(j -> cc[j] = reduce.applyAsLong( + cc[j + 1], + cs[j] = Math.max(cs[j], ints[i][j]) + )); + return Arrays.copyOf(cc, ints[i].length); + }) + .toArray(long[][]::new); + return range(ints.length).mapToObj(i -> rows[i]).toArray(long[][]::new); + }; + } + + private static IntStream range(final int length) { + return IntStream.iterate(length - 1, i -> i >= 0, i -> i - 1); + } + + + // === Rotate + public static final Named ROTATE = Named.of("Rotate", ints -> { + final List rows = new ArrayList<>(List.of(ints)); + return IntStream.range(0, Arrays.stream(ints).mapToInt(r -> r.length).max().orElse(0)) + .mapToObj(c -> { + rows.removeIf(r -> r.length <= c); + return range(rows.size()).mapToObj(rows::get).mapToLong(r -> r[c]).toArray(); + }) + .toArray(long[][]::new); + }); + + + // === Even + public static final Named EVEN = Named.of( + "Even", + ints -> ReverseTester.transform(IntStream.range(0, ints.length) + .mapToObj(i -> IntStream.range(0, ints[i].length) + .filter(j -> (i + j) % 2 == 0) + .map(j -> ints[i][j])) + .map(IntStream::toArray).toArray(int[][]::new)) + ); + + // Sum + @FunctionalInterface + interface LongTernaryOperator { + long applyAsLong(long a, long b, long c); + } + + public static final Named SUM = cross("Sum", 0, Long::sum, (r, c, v) -> r + c - v); + + private static long[][] cross( + final int[][] ints, + final IntToLongFunction map, + final LongBinaryOperator reduce, + final int zero, + final LongTernaryOperator get + ) { + // This code is intentionally obscure + final long[] rt = Arrays.stream(ints) + .map(Arrays::stream) + .mapToLong(row -> row.mapToLong(map).reduce(zero, reduce)) + .toArray(); + final long[] ct = new long[Arrays.stream(ints).mapToInt(r -> r.length).max().orElse(0)]; + Arrays.fill(ct, zero); + Arrays.stream(ints).forEach(r -> IntStream.range(0, r.length) + .forEach(i -> ct[i] = reduce.applyAsLong(ct[i], map.applyAsLong(r[i])))); + return IntStream.range(0, ints.length) + .mapToObj(r -> IntStream.range(0, ints[r].length) + .mapToLong(c -> get.applyAsLong(rt[r], ct[c], ints[r][c])) + .toArray()) + .toArray(long[][]::new); + } + + private static Named cross( + final String name, + final int zero, + final LongBinaryOperator reduce, + final LongTernaryOperator get + ) { + return Named.of(name, ints -> cross(ints, n -> n, reduce, zero, get)); + } + + public static final Named AVG = avg( + "Avg", + ints -> cross(ints, n -> n, Long::sum, 0, (r, c, v) -> r + c - v), + ints -> cross(ints, n -> 1, Long::sum, 0, (r1, c1, v1) -> r1 + c1 - 1) + ); + + private static Named avg( + final String name, + final Op fs, + final Op fc + ) { + return Named.of(name, ints -> avg(ints, fs.apply(ints), fc.apply(ints))); + } + + private static long[][] avg(final int[][] ints, final long[][] as, final long[][] ac) { + return IntStream.range(0, ints.length).mapToObj(i -> IntStream.range(0, ints[i].length) + .mapToLong(j -> as[i][j] / ac[i][j]) + .toArray()) + .toArray(long[][]::new); + } + + + // === Common + + public static final int MAX_SIZE = 10_000 / TestCounter.DENOMINATOR; + + public static final Selector SELECTOR = selector(ReverseTest.class, MAX_SIZE); + + private ReverseTest() { + // Utility class + } + + public static Selector selector(final Class owner, final int maxSize) { + return new Selector(owner) + .variant("Base", ReverseTester.variant(maxSize, REVERSE)) + .variant("3637", ReverseTester.variant(maxSize, MAX_C)) + .variant("3839", ReverseTester.variant(maxSize, MAX)) + .variant("3435", ReverseTester.variant(maxSize, ROTATE)) + .variant("3233", ReverseTester.variant(maxSize, EVEN)) + .variant("4142", ReverseTester.variant(maxSize, AVG)) + .variant("4749", ReverseTester.variant(maxSize, SUM)) + ; + } + + public static void main(final String... args) { + SELECTOR.main(args); + } +} diff --git a/java/reverse/ReverseTester.java b/java/reverse/ReverseTester.java new file mode 100644 index 0000000..134f0fd --- /dev/null +++ b/java/reverse/ReverseTester.java @@ -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 TRANSFORM = Named.of("", ReverseTester::transform); + public static final Named SPACE = Named.of("", " "); + + @FunctionalInterface + public interface Op extends Function {} + + private static final int[] DIVISORS = {100, 10, 1}; + + private final Op transform; + private final BiFunction inputToString; + private final BiFunction outputToString; + private final String name; + private final String spaces; + + private ReverseTester(final String className, final Op transform, final String spaces) { + this(className, transform, spaces, (r, i) -> Integer.toString(i), (r, i) -> Long.toString(i)); + } + + private ReverseTester( + final String className, + final Op transform, + final String spaces, + final BiFunction inputToString, + final BiFunction outputToString + ) { + name = className; + this.transform = transform; + this.spaces = spaces; + this.inputToString = inputToString; + this.outputToString = outputToString; + } + + private static Consumer variant(final int maxSize, final Supplier tester) { + return counter -> tester.get().run(counter, maxSize); + } + + public static Consumer variant(final int maxSize, final Named transform) { + return variant(maxSize, transform, SPACE); + } + + + public static Consumer variant(final int maxSize, final Named transform, final Named spaces) { + Objects.requireNonNull(transform); + Objects.requireNonNull(spaces); + return variant( + maxSize, + () -> new ReverseTester("Reverse" + transform.name() + spaces.name(), transform.value(), spaces.value()) + ); + } + + public static Consumer variant( + final int maxSize, + final String suffix, + final Named> input, + final Named> output + ) { + return variant(maxSize, suffix, TRANSFORM, input, output); + } + + public static Consumer variant( + final int maxSize, + final String suffix, + final Named op, + final Named> input, + final Named> output + ) { + return variant(maxSize, suffix, op, input, output, SPACE); + } + + public static Consumer variant( + final int maxSize, + final String suffix, + final Named op, + final Named> input, + final Named> output, + final Named spaces + ) { + final String out = input.name().contains(output.name()) ? "" : output.name(); + return variant(maxSize, () -> new ReverseTester( + "Reverse" + op.name() + input.name() + out + suffix + spaces.name(), + op.value(), + spaces.value(), + input.value(), + output.value() + )); + } + + private void run(final TestCounter counter, final int maxSize) { + new Checker(counter, maxSize, Runner.packages("", "reverse").std(name), spaces).test(); + } + + @Override + public String toString() { + return name; + } + + public static long[][] transform(final int[][] ints) { + return IntStream.range(1, ints.length + 1) + .mapToObj(i -> ints[ints.length - i]) + .map(is -> IntStream.range(1, is.length + 1).mapToLong(i -> is[is.length - i]).toArray()) + .toArray(long[][]::new); + } + + /** + * @author Georgiy Korneev (kgeorgiy@kgeorgiy.info) + */ + private class Checker extends BaseChecker { + private final int maxSize; + private final Runner runner; + private final String spaces; + private final Set manualTests = new HashSet<>(); + + Checker(final TestCounter counter, final int maxSize, final Runner runner, final String spaces) { + super(counter); + this.maxSize = maxSize; + this.runner = runner; + this.spaces = spaces; + } + + public void manualTest(final int[][] ints) { + for (final List permutation : permutations(new ArrayList<>(Arrays.asList(ints)))) { + final int[][] input = permutation.toArray(int[][]::new); + final String[][] lines = toString(input, inputToString); + if (manualTests.add(Arrays.deepToString(lines))) { + test(lines, toString(transform.apply(input), outputToString)); + } + } + } + + public void test(final int[][] ints) { + test(toString(ints, inputToString), toString(transform.apply(ints), outputToString)); + } + + public void test(final String[][] input, final String[][] output) { + final List inputLines = toLines(input, random().randomString(spaces, 1, 10)); + final List outputLines = toLines(output, " "); + runner.testEquals(counter, inputLines, outputLines); + } + + private String[][] toString(final int[][] ints, final BiFunction toString) { + return Arrays.stream(ints) + .map(row -> Arrays.stream(row).mapToObj(i -> toString.apply(random(), i)).toArray(String[]::new)) + .toArray(String[][]::new); + } + + private String[][] toString(final long[][] ints, final BiFunction toString) { + return Arrays.stream(ints) + .map(row -> Arrays.stream(row).mapToObj(i -> toString.apply(random(), (int) i)).toArray(String[]::new)) + .toArray(String[][]::new); + } + + private List toLines(final String[][] data, final String delimiter) { + if (data.length == 0) { + return Collections.singletonList(""); + } + return Arrays.stream(data) + .map(row -> String.join(delimiter, row)) + .collect(Collectors.toList()); + } + + public int[][] random(final int[] profile) { + final int col = random().nextInt(Arrays.stream(profile).max().orElse(0)); + final int row = random().nextInt(profile.length); + final int m = random().nextInt(5) - 2; + final int[][] ints = Arrays.stream(profile).mapToObj(random().getRandom()::ints).map(IntStream::toArray).toArray(int[][]::new); + Arrays.stream(ints).filter(r -> col < r.length).forEach(r -> r[col] = Math.abs(r[col]) / 2 * m); + ints[row] = Arrays.stream(ints[row]).map(Math::abs).map(v -> v / 2 * m).toArray(); + return ints; + } + + public void test() { + manualTest(new int[][]{ + {1} + }); + manualTest(new int[][]{ + {1, 2}, + {3} + }); + manualTest(new int[][]{ + {1, 2, 3}, + {4, 5}, + {6} + }); + manualTest(new int[][]{ + {1, 2, 3}, + {}, + {4, 5}, + {6} + }); + manualTest(new int[][]{ + {1, 2, 3}, + {-4, -5}, + {6} + }); + manualTest(new int[][]{ + {1, -2, 3}, + {}, + {4, -5}, + {6} + }); + manualTest(new int[][]{ + {1, 2, 0}, + {1, 0}, + {0}, + }); + manualTest(new int[][]{ + {1}, + {1, 3}, + {1, 2, 3}, + }); + manualTest(new int[][]{ + {-1}, + {-1, -2}, + {-1, -2, -3}, + }); + manualTest(new int[][]{ + {}, + }); + manualTest(new int[][]{ + {}, + {}, + {}, + }); + testRandom(tweakProfile(constProfile(10, 10), new int[][]{})); + testRandom(tweakProfile(constProfile(100, 100), new int[][]{})); + testRandom(randomProfile(100, maxSize)); + testRandom(randomProfile(maxSize / 10, maxSize)); + testRandom(randomProfile(maxSize, maxSize)); + for (final int d : DIVISORS) { + final int size = maxSize / d; + testRandom(tweakProfile(constProfile(size / 2, 0), new int[][]{{size / 2, 0}})); + testRandom(tweakProfile(randomProfile(size, size / 2), new int[][]{{size / 2, 0}})); + testRandom(tweakProfile(constProfile(size / 2, 0), new int[][]{{size / 2, size / 2 - 1}})); + testRandom(tweakProfile(constProfile(size / 3, 1), new int[][]{{size / 3, size / 6, size / 3 - 1}})); + } + } + + private int[] randomProfile(final int length, final int values) { + final int[] profile = new int[length]; + for (int i = 0; i < values; i++) { + profile[random().nextInt(0, length - 1)]++; + } + return profile; + } + + private void testRandom(final int[] profile) { + test(random(profile)); + } + + private int[] constProfile(final int length, final int value) { + final int[] profile = new int[length]; + Arrays.fill(profile, value); + return profile; + } + + private int[] tweakProfile(final int[] profile, final int[][] mods) { + for (final int[] mod : mods) { + Arrays.stream(mod).skip(1).forEach(i -> profile[i] = mod[0]); + } + return profile; + } + } + + private static List> permutations(final List elements) { + final List> result = new ArrayList<>(); + permutations(new ArrayList<>(elements), result, elements.size() - 1); + return result; + } + + private static void permutations(final List elements, final List> result, final int n) { + if (n == 0) { + result.add(List.copyOf(elements)); + } else { + for (int i = 0; i < n; i++) { + permutations(elements, result, n - 1); + if (n % 2 == 1) { + Collections.swap(elements, i, n); + } else { + Collections.swap(elements, 0, n); + } + } + permutations(elements, result, n - 1); + } + } +} diff --git a/java/reverse/package-info.java b/java/reverse/package-info.java new file mode 100644 index 0000000..86e0e5d --- /dev/null +++ b/java/reverse/package-info.java @@ -0,0 +1,7 @@ +/** + * Tests for Reverse homework + * of Introduction to Programming course. + * + * @author Georgiy Korneev (kgeorgiy@kgeorgiy.info) + */ +package reverse; \ No newline at end of file diff --git a/java/sum/Sum.java b/java/sum/Sum.java new file mode 100644 index 0000000..ef35486 --- /dev/null +++ b/java/sum/Sum.java @@ -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); + } +} diff --git a/java/sum/SumBigDecimalHex.java b/java/sum/SumBigDecimalHex.java new file mode 100644 index 0000000..6c50146 --- /dev/null +++ b/java/sum/SumBigDecimalHex.java @@ -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; + } +} diff --git a/java/sum/SumBigIntegerOctal.java b/java/sum/SumBigIntegerOctal.java new file mode 100644 index 0000000..b65b35b --- /dev/null +++ b/java/sum/SumBigIntegerOctal.java @@ -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; + } +} diff --git a/java/sum/SumDouble.java b/java/sum/SumDouble.java new file mode 100644 index 0000000..57fb236 --- /dev/null +++ b/java/sum/SumDouble.java @@ -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); + } +} diff --git a/java/sum/SumDoubleHex.java b/java/sum/SumDoubleHex.java new file mode 100644 index 0000000..2de0e78 --- /dev/null +++ b/java/sum/SumDoubleHex.java @@ -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; + } +} diff --git a/java/sum/SumHex.java b/java/sum/SumHex.java new file mode 100644 index 0000000..f18906d --- /dev/null +++ b/java/sum/SumHex.java @@ -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; + } +} diff --git a/java/sum/SumLongOctal.java b/java/sum/SumLongOctal.java new file mode 100644 index 0000000..472fb5e --- /dev/null +++ b/java/sum/SumLongOctal.java @@ -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); + } + } +} diff --git a/java/sum/SumTest.java b/java/sum/SumTest.java new file mode 100644 index 0000000..bdd37b3 --- /dev/null +++ b/java/sum/SumTest.java @@ -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 extends UnaryOperator> {} + + private static final BiConsumer TO_STRING = (expected, out) -> Asserts.assertEquals("Sum", expected.toString(), out); + + private static final Named>> 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 Named> plain() { + return Named.of("", test -> test); + } + + + // === DoubleHex + + private static BiConsumer approximate(final Function parser, final double precision) { + return (expected, out) -> + Asserts.assertEquals("Sum", expected.doubleValue(), parser.apply(out).doubleValue(), precision); + } + + private static final Named>> 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 Named> hexFull(final Function toHex) { + final Function 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 Named> octal(final Function 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>> 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>> 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>> 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 Named> hex(final Function toHex) { + return hexFull(v -> "0x" + toHex.apply(v)); + } + + + // === Common + + /* package-private */ static Consumer variant( + final Named> runner, + final Named>> test, + final Named, ? 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> 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> 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); + } +} diff --git a/java/sum/SumTester.java b/java/sum/SumTester.java new file mode 100644 index 0000000..404e4b3 --- /dev/null +++ b/java/sum/SumTester.java @@ -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 { + private static final List 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 add; + private final LongFunction fromLong; + private BiFunction toString; + private final BiFunction randomValue; + private final BiConsumer verifier; + private List spaces; + private final List limits; + + private final List> tests = new ArrayList<>(); + + @SafeVarargs + public SumTester( + final BinaryOperator add, + final LongFunction fromLong, + final BiFunction randomValue, + final BiConsumer 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 setSpaces(final List spaces) { + this.spaces = spaces; + return this; + } + + protected SumTester addSpaces(final String... spaces) { + this.spaces = Stream.concat(this.spaces.stream(), Arrays.stream(spaces)).toList(); + return this; + } + + public SumTester setToString(final Function toString) { + return setToString((r, n) -> toString.apply(n)); + } + + public SumTester setToString(final BiFunction toString) { + this.toString = toString; + return this; + } + + protected SumTester test(final Function toString, final long... input) { + return testT( + fromLong.apply(LongStream.of(input).sum()), + LongStream.of(input).mapToObj(fromLong).map(toString).collect(Collectors.joining(" ")) + ); + } + + protected SumTester test(final long result, final String... input) { + return testT(fromLong.apply(result), input); + } + + protected SumTester testT(final T result, final String... input) { + return testT(result, Arrays.asList(input)); + } + + private SumTester testT(final T result, final List input) { + tests.add(checker -> checker.test(result, input)); + return this; + } + + public SumTester 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 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 input) { + counter.test(() -> { + final List 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 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 args, final String spaces) { + final List spaced = args.stream() + .map(n -> toString.apply(random(), n)) + .map(value -> randomSpace(spaces) + value + randomSpace(spaces)) + .toList(); + final List argsList = new ArrayList<>(); + for (final Iterator 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); + } + } +} diff --git a/java/sum/package-info.java b/java/sum/package-info.java new file mode 100644 index 0000000..04ce7fb --- /dev/null +++ b/java/sum/package-info.java @@ -0,0 +1,7 @@ +/** + * Tests for Sum homework + * of Introduction to Programming course. + * + * @author Georgiy Korneev (kgeorgiy@kgeorgiy.info) + */ +package sum; \ No newline at end of file diff --git a/java/wordStat/WordInfo.java b/java/wordStat/WordInfo.java new file mode 100644 index 0000000..b2a726a --- /dev/null +++ b/java/wordStat/WordInfo.java @@ -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; + } +} diff --git a/java/wordStat/WordStat.java b/java/wordStat/WordStat.java new file mode 100644 index 0000000..536a1fe --- /dev/null +++ b/java/wordStat/WordStat.java @@ -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 "); + } + + String inputFileName = args[0]; + String outputFileName = args[1]; + try { + BufferedReader r = new BufferedReader( + new FileReader(inputFileName) + ); + + LinkedHashMap 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 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()); + } + } +} diff --git a/java/wordStat/WordStatChecker.java b/java/wordStat/WordStatChecker.java new file mode 100644 index 0000000..cc19c83 --- /dev/null +++ b/java/wordStat/WordStatChecker.java @@ -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>> processor; + + private final MainChecker main; + + private WordStatChecker( + final String className, + final Function>> 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>> processor, + final Consumer 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>> 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> expected) { + final List 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> 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; + } +} diff --git a/java/wordStat/WordStatLength.java b/java/wordStat/WordStatLength.java new file mode 100644 index 0000000..23fdc55 --- /dev/null +++ b/java/wordStat/WordStatLength.java @@ -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 " + ); + } + + String inputFileName = args[0]; + String outputFileName = args[1]; + try { + BufferedReader r = new BufferedReader( + new FileReader(inputFileName) + ); + + Map 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 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()); + } + } +} diff --git a/java/wordStat/WordStatLengthAffix.java b/java/wordStat/WordStatLengthAffix.java new file mode 100644 index 0000000..e4f53c2 --- /dev/null +++ b/java/wordStat/WordStatLengthAffix.java @@ -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 " + ); + } + + String inputFileName = args[0]; + String outputFileName = args[1]; + try { + BufferedReader r = new BufferedReader( + new FileReader(inputFileName) + ); + + Map 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 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()); + } + } +} diff --git a/java/wordStat/WordStatLengthMiddle.java b/java/wordStat/WordStatLengthMiddle.java new file mode 100644 index 0000000..4063ecb --- /dev/null +++ b/java/wordStat/WordStatLengthMiddle.java @@ -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 " + ); + } + + String inputFileName = args[0]; + String outputFileName = args[1]; + try { + BufferedReader r = new BufferedReader( + new FileReader(inputFileName) + ); + + Map 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 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()); + } + } +} diff --git a/java/wordStat/WordStatLengthPrefix.java b/java/wordStat/WordStatLengthPrefix.java new file mode 100644 index 0000000..ee56651 --- /dev/null +++ b/java/wordStat/WordStatLengthPrefix.java @@ -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 " + ); + } + + String inputFileName = args[0]; + String outputFileName = args[1]; + try { + BufferedReader r = new BufferedReader( + new FileReader(inputFileName) + ); + + Map 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 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()); + } + } +} diff --git a/java/wordStat/WordStatLengthSuffix.java b/java/wordStat/WordStatLengthSuffix.java new file mode 100644 index 0000000..bc1bcf0 --- /dev/null +++ b/java/wordStat/WordStatLengthSuffix.java @@ -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 " + ); + } + + String inputFileName = args[0]; + String outputFileName = args[1]; + try { + BufferedReader r = new BufferedReader( + new FileReader(inputFileName) + ); + + Map 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 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()); + } + } +} diff --git a/java/wordStat/WordStatTest.java b/java/wordStat/WordStatTest.java new file mode 100644 index 0000000..151991e --- /dev/null +++ b/java/wordStat/WordStatTest.java @@ -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 Word Statistics homework + * of Introduction to Programming course. + * + * @author Georgiy Korneev (kgeorgiy@kgeorgiy.info) + */ +public final class WordStatTest { + // === Base + private static final Named>> 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>> MIDDLE = + size("Middle", SIZE * 2 + 1, s -> Stream.of(s.substring(SIZE, s.length() - SIZE))); + + static Named>> size( + final String name, + final int length, + final Function> f + ) { + return Named.of(name, s -> s.length() >= length ? f.apply(s) : Stream.empty()); + } + + // === 3839 + private static final Named>> 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>> SUFFIX = + size("Suffix", 2, s -> Stream.of(s.substring(s.length() - s.length() / 2))); + + // === 4749 + private static final Named>> 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); + } +} diff --git a/java/wordStat/WordStatTester.java b/java/wordStat/WordStatTester.java new file mode 100644 index 0000000..5534c14 --- /dev/null +++ b/java/wordStat/WordStatTester.java @@ -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 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> c) { + public Consumer with(final Named>> 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> answer(final Function> split, final String[][] text) { + final List 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(); + } + } +} diff --git a/java/wordStat/package-info.java b/java/wordStat/package-info.java new file mode 100644 index 0000000..b54b5f6 --- /dev/null +++ b/java/wordStat/package-info.java @@ -0,0 +1,7 @@ +/** + * Tests for Word Statistics homework + * of Introduction to Programming course. + * + * @author Georgiy Korneev (kgeorgiy@kgeorgiy.info) + */ +package wordStat; \ No newline at end of file diff --git a/java/wspp/IntList.java b/java/wspp/IntList.java new file mode 100644 index 0000000..81fdfcd --- /dev/null +++ b/java/wspp/IntList.java @@ -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(); + } +} diff --git a/java/wspp/WordInfo.java b/java/wspp/WordInfo.java new file mode 100644 index 0000000..bbbd1af --- /dev/null +++ b/java/wspp/WordInfo.java @@ -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 lineOccurrences = new HashMap<>(); + + public WordInfo(String word) { + this.word = word; + } + + public WordInfo(String word, int firstOccurrence) { + this.word = word; + this.firstOccurrence = firstOccurrence; + } +} diff --git a/java/wspp/Wspp.java b/java/wspp/Wspp.java new file mode 100644 index 0000000..93d9b13 --- /dev/null +++ b/java/wspp/Wspp.java @@ -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 " + ); + } + + final String inputFileName = args[0]; + final String outputFileName = args[1]; + + Map 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."); + } + } +} diff --git a/java/wspp/WsppLast.java b/java/wspp/WsppLast.java new file mode 100644 index 0000000..e755954 --- /dev/null +++ b/java/wspp/WsppLast.java @@ -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 " + ); + } + + final String inputFileName = args[0]; + final String outputFileName = args[1]; + + Map 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 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."); + } + } +} diff --git a/java/wspp/WsppMiddle.java b/java/wspp/WsppMiddle.java new file mode 100644 index 0000000..a0f1f1b --- /dev/null +++ b/java/wspp/WsppMiddle.java @@ -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 " + ); + } + + final String inputFileName = args[0]; + final String outputFileName = args[1]; + + Map 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 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."); + } + } +} diff --git a/java/wspp/WsppPos.java b/java/wspp/WsppPos.java new file mode 100644 index 0000000..1293a81 --- /dev/null +++ b/java/wspp/WsppPos.java @@ -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 " + ); + } + + final String inputFileName = args[0]; + final String outputFileName = args[1]; + + Map 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."); + } + } +} diff --git a/java/wspp/WsppPosition.java b/java/wspp/WsppPosition.java new file mode 100644 index 0000000..2febbbb --- /dev/null +++ b/java/wspp/WsppPosition.java @@ -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 " + ); + } + + final String inputFileName = args[0]; + final String outputFileName = args[1]; + + Map 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 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."); + } + } +} diff --git a/java/wspp/WsppTest.java b/java/wspp/WsppTest.java new file mode 100644 index 0000000..16cdebc --- /dev/null +++ b/java/wspp/WsppTest.java @@ -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>> INPUT = Named.of("", Comparator.comparingInt(e -> 0)); + private static final Named> ALL = Named.of("", size -> IntStream.range(0, size)); + private static final Named> WSPP = Named.of("", (r, l, L, g, G) -> g); + private static final Named NONE = Named.of("", ""); + + // === 3637 + private static final Named>> LENGTH = Named.of("", + Map.Entry.comparingByKey(Comparator.comparingInt(String::length))); + private static final Named> LAST = Named.of("Last", size -> IntStream.of(size - 1)); + private static final Named JAVA = Named.of("", "XHB7TmR9JF8="); + + // === 3839 + private static final Named> MIDDLE = Named.of("Middle", size -> IntStream.of(size / 2)); + + // === 3435 + public static final WsppTester.Extractor 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); + } +} diff --git a/java/wspp/WsppTester.java b/java/wspp/WsppTester.java new file mode 100644 index 0000000..ba571a5 --- /dev/null +++ b/java/wspp/WsppTester.java @@ -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 Consumer variant( + final Named>> comparator, + final Named> selector, + final Named> extractor, + final Named 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 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 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 select(int l, int li, int lt, int gi, int gt); + } +} diff --git a/java/wspp/package-info.java b/java/wspp/package-info.java new file mode 100644 index 0000000..117ede7 --- /dev/null +++ b/java/wspp/package-info.java @@ -0,0 +1,7 @@ +/** + * Tests for Word Statistics++ homework + * of Introduction to Programming course. + * + * @author Georgiy Korneev (kgeorgiy@kgeorgiy.info) + */ +package wspp; \ No newline at end of file diff --git a/lectures/README.md b/lectures/README.md new file mode 100644 index 0000000..c9fc9ec --- /dev/null +++ b/lectures/README.md @@ -0,0 +1,1763 @@ +--- +gitea: none +include_toc: true +--- + +# Конспект лекции 2: Массивы и ссылки +**Преподаватель:** Георгий Корнеев +**Курс:** Введение в программирование (Java) + +--- + +## Содержание +1. [Одномерные массивы](#1-одномерные-массивы) +2. [Многомерные массивы](#2-многомерные-массивы) +3. [Ссылки на массивы](#3-ссылки-на-массивы) +4. [Полные имена классов](#4-полные-имена-классов) +5. [Класс Scanner](#5-класс-scanner) +6. [Домашнее задание](#6-домашнее-задание) + +--- + +## 1. Одномерные массивы + +### 1.1. Типы данных в Java (повторение) + +**Классификация типов:** +``` +Типы данных +├── Примитивные (primitive) +│ ├── int, long +│ ├── char +│ ├── boolean +│ └── double, float +└── Ссылочные (reference) + ├── String + ├── Массивы + └── Объекты классов +``` + +⚠️ **Важно:** Массивы — это ссылочные типы! + +--- + +### 1.2. Объявление массивов + +#### Синтаксис объявления +```java +int[] ints; // Массив целых чисел +double[] doubles; // Массив вещественных чисел +boolean[] booleans; // Массив булевых значений +String[] strings; // Массив строк +char[] chars; // Массив символов +long[] longs; // Массив длинных целых +``` + +**Важные особенности:** +- Квадратные скобки `[]` указывают на тип "массив" +- Можно создавать массивы любых типов (примитивных и ссылочных) + +--- + +### 1.3. Создание массивов + +#### Способ 1: Указание размера +```java +ints = new int[10]; // Массив из 10 элементов +doubles = new double[20]; // Массив из 20 элементов +booleans = new boolean[30]; +strings = new String[40]; +chars = new char[5]; +longs = new long[3]; +``` + +#### Способ 2: Инициализация значениями +```java +ints = new int[]{1, 2, 3}; +doubles = new double[]{1.5}; +booleans = new boolean[]{true, false}; +strings = new String[]{"a", "b", "c"}; +``` + +#### Способ 3: Сокращённая инициализация (при объявлении) +```java +int[] ints = {1, 2, 3}; +String[] strings = {"hello", "world", "array"}; +``` + +⚠️ **Нельзя комбинировать:** `new int[5]{1, 2, 3, 4, 5}` — **НЕ компилируется!** + +--- + +### 1.4. Длина массива + +```java +int[] array = new int[10]; +System.out.println(array.length); // 10 (поле, НЕ метод!) +``` + +**Важно:** +- `length` — это **поле** (property), не метод +- Пишется **без скобок**: `array.length`, а не `array.length()` + +--- + +### 1.5. Значения по умолчанию + +При создании массива с указанием только размера элементы инициализируются значениями по умолчанию: + +| Тип | Значение по умолчанию | +|-----|----------------------| +| `int`, `long` | `0` | +| `double`, `float` | `0.0` | +| `boolean` | `false` | +| `char` | `'\0'` (символ с кодом 0) | +| Ссылочные типы (`String`, массивы) | `null` | + +```java +int[] ints = new int[10]; +System.out.println(ints[0]); // 0 + +boolean[] booleans = new boolean[10]; +System.out.println(booleans[0]); // false + +String[] strings = new String[10]; +System.out.println(strings[0]); // null +``` + +--- + +### 1.6. Доступ к элементам массива + +```java +int[] array = {1, 2, 3, 4, 5}; + +// Чтение +int first = array[0]; // 1 (нумерация с 0!) +int last = array[4]; // 5 + +// Запись +array[0] = 10; +array[2] = 30; + +// Результат: {10, 2, 30, 4, 5} +``` + +**Важно:** Индексация начинается с **0**! + +--- + +### 1.7. Ошибки при работе с массивами + +#### Отрицательная длина +```java +int[] array = new int[-5]; +// Исключение: NegativeArraySizeException +``` + +#### Выход за границы +```java +int[] array = new int[3]; +System.out.println(array[5]); +// Исключение: ArrayIndexOutOfBoundsException +``` + +#### Обращение к null +```java +int[] array = null; +System.out.println(array.length); +// Исключение: NullPointerException +``` + +--- + +### 1.8. Итерация по массивам + +#### Цикл for (с индексом) +```java +int[] ints = {1, 2, 3, 4, 5}; + +// i — это индекс +for (int i = 0; i < ints.length; i++) { + System.out.println(ints[i]); +} +``` + +**Когда использовать:** +- Нужен индекс элемента +- Нужен доступ на запись +- Нужна итерация с шагом (не по порядку) + +#### Цикл for-each (без индекса) +```java +int[] ints = {1, 2, 3, 4, 5}; + +// i — это значение элемента +for (int i : ints) { + System.out.println(i); +} +``` + +**Когда использовать:** +- Нужно только читать значения +- Итерация всегда последовательная +- Индекс не нужен + +**Преимущества for-each:** +- Более читаемый код +- Меньше ошибок (нет индексов) +- Работает с любыми коллекциями + +--- + +### 1.9. Максимальная длина массива + +```java +// Максимальное значение int +int maxSize = Integer.MAX_VALUE; // 2,147,483,647 + +// НО! Массив такой длины создать НЕЛЬЗЯ +int[] huge = new int[Integer.MAX_VALUE]; +// Исключение: OutOfMemoryError +``` + +**Ограничения:** +- Длина массива имеет тип `int` +- Физически ограничено доступной памятью +- JVM имеет лимиты на размер heap (куча) + +**Пример работы с памятью:** +```java +// Настройка heap для JVM +// java -Xmx16G MyProgram — выделить 16 GB +// java -Xmx1G MyProgram — выделить 1 GB + +// Создание большого массива +int[] array = new int[100_000_000]; // 100 млн элементов +// Занимает: 100,000,000 × 4 bytes = ~400 MB +``` + +--- + +### 1.10. Динамический размер массива + +Размер может определяться во время выполнения: + +```java +public static void main(String[] args) { + // Первый аргумент командной строки + int size = Integer.parseInt(args[0]); + + // Создаём массив динамического размера + int[] array = new int[size]; + + System.out.println("Создан массив длины: " + array.length); +} +``` + +```bash +# Запуск программы +java MyProgram 123 +# Вывод: Создан массив длины: 123 +``` + +--- + +## 2. Многомерные массивы + +### 2.1. Концепция многомерных массивов + +**Важная идея:** В Java многомерные массивы — это **массивы массивов**! + +``` +Двумерный массив: +int[][] matrix - это массив, каждый элемент которого — массив int[] + +Трёхмерный массив: +int[][][] cube - это массив, каждый элемент которого — массив int[][] +``` + +--- + +### 2.2. Объявление многомерных массивов + +```java +int[][] ints2d; // Двумерный массив +int[][][] ints3d; // Трёхмерный массив +String[][] strings2d; // Двумерный массив строк +``` + +--- + +### 2.3. Создание многомерных массивов + +#### Полное создание (прямоугольный массив) +```java +// Матрица 10×20 (10 строк, 20 столбцов) +int[][] matrix = new int[10][20]; + +// Куб 10×20×30 +int[][][] cube = new int[10][20][30]; + +System.out.println(matrix[0]); // [I@... (ссылка на массив int[]) +System.out.println(cube[0]); // [[I@... (ссылка на int[][]) +``` + +#### Частичное создание +```java +// Создаём только первое измерение +int[][] matrix = new int[10][]; +int[][][] cube = new int[10][][]; + +System.out.println(matrix[0]); // null (элементы не созданы!) +System.out.println(cube[0]); // null +``` + +**Использование:** +```java +int[][] matrix = new int[10][]; + +// Заполняем вручную +for (int i = 0; i < matrix.length; i++) { + matrix[i] = new int[i]; // Каждая строка своей длины! +} +``` + +--- + +### 2.4. Непрямоугольные массивы + +Java позволяет создавать **"рваные" массивы** (jagged arrays) — массивы, где строки имеют разную длину: + +```java +// Создание непрямоугольного массива +int[][] jagged = new int[3][]; +jagged[0] = new int[5]; // Первая строка — 5 элементов +jagged[1] = new int[2]; // Вторая строка — 2 элемента +jagged[2] = new int[7]; // Третья строка — 7 элементов + +// Проверка длин +for (int[] row : jagged) { + System.out.println(row.length); // 5, 2, 7 +} +``` + +#### Инициализация непрямоугольного массива +```java +int[][] jagged = new int[][]{ + {1, 2, 3}, // Строка из 3 элементов + {4, 5}, // Строка из 2 элементов + {6, 7, 8, 9}, // Строка из 4 элементов + null // Пустая строка (null) +}; + +System.out.println(jagged.length); // 4 (количество строк) +System.out.println(jagged[0].length); // 3 +System.out.println(jagged[1].length); // 2 +System.out.println(jagged[2].length); // 4 +System.out.println(jagged[3]); // null +``` + +⚠️ **Внимание:** `jagged[3].length` вызовет `NullPointerException`! + +--- + +### 2.5. Работа с двумерными массивами + +#### Итерация по всем элементам +```java +int[][] matrix = { + {1, 2, 3}, + {4, 5, 6}, + {7, 8, 9} +}; + +// Вложенные циклы for +for (int i = 0; i < matrix.length; i++) { + for (int j = 0; j < matrix[i].length; j++) { + System.out.print(matrix[i][j] + " "); + } + System.out.println(); +} + +// Вложенные циклы for-each +for (int[] row : matrix) { + for (int element : row) { + System.out.print(element + " "); + } + System.out.println(); +} +``` + +--- + +### 2.6. Значения по умолчанию в многомерных массивах + +```java +int[][] matrix = new int[3][4]; + +// Все элементы инициализированы нулями +System.out.println(matrix[0][0]); // 0 + +String[][] strings = new String[3][4]; + +// Все элементы равны null +System.out.println(strings[0][0]); // null +``` + +--- + +## 3. Ссылки на массивы + +### 3.1. Массивы как ссылочный тип + +**Ключевая концепция:** Переменная массива хранит не сам массив, а **ссылку** на него в памяти. + +```java +int[] a = new int[10]; // a содержит ссылку на массив +``` + +``` +Память: +┌─────────┐ ┌───────────────┐ +│ a │───────>│ [0,0,0,...,0] │ +└─────────┘ └───────────────┘ +переменная массив в heap +``` + +--- + +### 3.2. Копирование ссылок + +```java +int[] a = new int[10]; +int[] b = a; // b указывает на ТОТ ЖЕ массив! + +a[1] = 10; +System.out.println(a[1] + " " + b[1]); // 10 10 + +System.out.println(a == b); // true (одна и та же ссылка) +``` + +``` +Память: +┌─────────┐ ┌───────────────┐ +│ a │───────>│ [0,10,0,...,0]│ +└─────────┘ ┌──>└───────────────┘ +┌─────────┐ │ +│ b │────┘ +└─────────┘ +``` + +--- + +### 3.3. Переприсваивание ссылок + +```java +int[] a = new int[10]; +int[] b = a; + +a = new int[10]; // a теперь указывает на НОВЫЙ массив! + +a[1] = 20; +System.out.println(a[1] + " " + b[1]); // 20 10 + +System.out.println(a == b); // false (разные массивы) +``` + +``` +До: a и b указывают на один массив +┌─────┐ ┌───────────────┐ +│ a │───>│ [0,0,0,...,0] │ +└─────┘ ┌─>└───────────────┘ +┌─────┐ │ +│ b │─┘ +└─────┘ + +После: a указывает на новый массив +┌─────┐ ┌───────────────┐ +│ a │───>│ [0,20,0,...,0]│ +└─────┘ └───────────────┘ +┌─────┐ ┌───────────────┐ +│ b │───>│ [0,0,0,...,0] │ +└─────┘ └───────────────┘ +``` + +--- + +### 3.4. Передача массивов в функции + +#### Изменение элементов массива +```java +void fill(int[] ints, int value) { + for (int i = 0; i < ints.length; i++) { + ints[i] = value; + } +} + +// Использование +int[] array = new int[3]; +fill(array, 100); +System.out.println(Arrays.toString(array)); // [100, 100, 100] +``` + +✅ **Изменения видны снаружи** — мы меняем элементы по ссылке! + +--- + +#### Переприсваивание параметра (НЕ работает!) +```java +void referenceAsValue(int[] ints) { + ints = new int[0]; // Локальное изменение параметра +} + +// Использование +int[] a = new int[10]; +int[] b = a; +referenceAsValue(a); +System.out.println(a == b); // true (массив НЕ изменился!) +``` + +❌ **Переприсваивание НЕ видно снаружи** — параметры передаются по значению! + +**Почему так происходит:** +``` +При вызове функции: +1. Копируется ССЫЛКА (не сам массив) +2. Локальный параметр указывает на тот же массив +3. Переприсваивание меняет только локальную переменную +4. Исходная переменная остаётся без изменений +``` + +--- + +### 3.5. Возврат массива из функции + +```java +int[] create(int length, int value) { + int[] ints = new int[length]; + for (int i = 0; i < ints.length; i++) { + ints[i] = value; + } + return ints; // Возвращаем ссылку на созданный массив +} + +// Использование +int[] array = create(3, 123); +System.out.println(Arrays.toString(array)); // [123, 123, 123] +``` + +✅ **Массив создаётся в heap** и продолжает существовать после выхода из функции! + +--- + +### 3.6. Сборка мусора (Garbage Collection) + +Java автоматически освобождает память от неиспользуемых объектов: + +```java +int[] a = new int[1_000_000]; + +// Создаём 10 миллионов массивов +for (int i = 0; i < 10_000_000; i++) { + a = new int[1_000_000]; // Старый массив становится мусором +} + +System.out.println("ok"); +// Программа работает! Сборщик мусора освобождает память +``` + +**Как это работает:** +1. Когда на объект не осталось ссылок, он становится "мусором" +2. Сборщик мусора периодически удаляет такие объекты +3. Память освобождается автоматически + +--- + +### 3.7. Копирование массивов + +#### Проблема с присваиванием +```java +int[] a = {1, 2, 3}; +int[] b = a; // Это НЕ копирование, это копирование ссылки! + +a[0] = 100; +System.out.println(b[0]); // 100 (изменили оба массива!) +``` + +#### Правильное копирование: System.arraycopy() +```java +int[] source = {0, 10, 20, 30, 40, 50}; +int[] dest = new int[5]; + +// System.arraycopy(откуда, откуда_индекс, куда, куда_индекс, сколько) +System.arraycopy(source, 2, dest, 1, 3); + +System.out.println(Arrays.toString(dest)); // [-1, 20, 30, 40, -1] +``` + +**Параметры:** +- `source` — исходный массив +- `2` — начальный индекс в источнике +- `dest` — массив назначения +- `1` — начальный индекс в назначении +- `3` — количество копируемых элементов + +**Схема:** +``` +source: [0, 10, 20, 30, 40, 50] + ^ ^ ^ + | | | + └───┼───┘ + ↓ +dest: [-1, 20, 30, 40, -1] + ^ ^ ^ ^ ^ +``` + +--- + +### 3.8. Особенности System.arraycopy() + +#### Корректная работа с перекрывающимися областями +```java +int[] array = {1, 2, 3, 4, 5}; + +// Копирование внутри одного массива (вправо) +System.arraycopy(array, 0, array, 2, 3); +// Результат: [1, 2, 1, 2, 3] + +// Копирование внутри одного массива (влево) +int[] array2 = {1, 2, 3, 4, 5}; +System.arraycopy(array2, 2, array2, 0, 3); +// Результат: [3, 4, 5, 4, 5] +``` + +✅ `System.arraycopy()` корректно обрабатывает перекрытия (использует временный буфер при необходимости) + +--- + +## 4. Полные имена классов + +### 4.1. Пакеты в Java + +Java использует **иерархическую систему пакетов** для организации классов: + +``` +java (корневой пакет) +├── lang (фундаментальные классы) +│ ├── String +│ ├── System +│ ├── Integer +│ ├── Object +│ └── Math +├── util (вспомогательные утилиты) +│ ├── Arrays +│ ├── Scanner +│ ├── ArrayList +│ └── zip (подпакет для сжатия) +│ ├── ZipFile +│ └── GZIPInputStream +└── io (ввод-вывод) + ├── File + ├── InputStream + └── OutputStream + +javax (дополнительные пакеты) +└── crypto (криптография) +``` + +--- + +### 4.2. Полные имена классов + +**Полное имя** = путь через пакеты + имя класса + +```java +// Полное имя класса System +java.lang.System + +// Полное имя класса Arrays +java.util.Arrays + +// Полное имя класса Scanner +java.util.Scanner +``` + +#### Использование полных имён +```java +// Можно использовать полное имя везде +java.util.Arrays.toString(array); +java.lang.System.out.println("Hello"); +java.lang.Integer.parseInt("123"); +``` + +--- + +### 4.3. Оператор import + +Чтобы не писать полные имена постоянно, используется `import`: + +```java +import java.util.Arrays; +import java.util.Scanner; +import java.lang.Integer; + +// Теперь можно использовать короткие имена +Arrays.toString(array); +Scanner scanner = new Scanner(System.in); +Integer.parseInt("123"); +``` + +#### Импорт всех классов из пакета +```java +import java.util.*; // Импортирует все классы из java.util + +Arrays.toString(array); // Работает +Scanner scanner = ...; // Работает +``` + +--- + +### 4.4. Неявный импорт java.lang + +**Важно:** Пакет `java.lang` **импортируется автоматически**! + +```java +// Эта строка присутствует неявно в каждой программе: +import java.lang.*; + +// Поэтому эти классы доступны без import: +String s = "hello"; +System.out.println(s); +Integer.parseInt("123"); +Math.sqrt(25); +``` + +--- + +### 4.5. Класс Arrays + +Класс `java.util.Arrays` содержит полезные методы для работы с массивами: + +```java +import java.util.Arrays; + +int[] array = {3, 1, 4, 1, 5}; + +// Преобразование в строку +String str = Arrays.toString(array); +System.out.println(str); // [3, 1, 4, 1, 5] + +// Заполнение массива +Arrays.fill(array, 7); +System.out.println(Arrays.toString(array)); // [7, 7, 7, 7, 7] + +// Сортировка +int[] data = {5, 2, 8, 1, 9}; +Arrays.sort(data); +System.out.println(Arrays.toString(data)); // [1, 2, 5, 8, 9] + +// Копирование +int[] copy = Arrays.copyOf(data, data.length); +``` + +--- + +### 4.6. Вывод массивов + +```java +int[] array = {0, 10, 20, 30}; + +// Прямой вывод (плохо!) +System.out.println(array); // [I@3834d63f (адрес в памяти) + +// Правильный вывод +System.out.println(Arrays.toString(array)); // [0, 10, 20, 30] +``` + +**Почему так происходит:** +- Массивы — ссылочный тип +- При прямом выводе печатается представление ссылки +- `[I@...` означает "массив int" + хеш-код + +--- + +## 5. Класс Scanner + +### 5.1. Назначение класса Scanner + +**Scanner** — класс для удобного чтения данных из различных источников: +- Стандартный ввод (консоль) +- Файлы +- Строки +- Потоки данных + +```java +import java.util.Scanner; +``` + +--- + +### 5.2. Создание Scanner для стандартного ввода + +```java +Scanner scanner = new Scanner(System.in); +``` + +**Параметры:** +- `System.in` — стандартный поток ввода (stdin) +- Можно также использовать `File`, `String`, `InputStream` и др. + +--- + +### 5.3. Основные методы Scanner + +#### Чтение различных типов данных +```java +Scanner scanner = new Scanner(System.in); + +// Чтение целого числа +int number = scanner.nextInt(); + +// Чтение вещественного числа +double value = scanner.nextDouble(); + +// Чтение строки (до пробела) +String word = scanner.next(); + +// Чтение целой строки (до конца строки) +String line = scanner.nextLine(); + +// Чтение символа (нет прямого метода, используем next()) +char ch = scanner.next().charAt(0); +``` + +--- + +### 5.4. Проверка наличия данных + +```java +Scanner scanner = new Scanner(System.in); + +// Проверка наличия следующего токена +while (scanner.hasNext()) { + String word = scanner.next(); + System.out.println(word); +} + +// Проверка наличия целого числа +while (scanner.hasNextInt()) { + int number = scanner.nextInt(); + System.out.println(number + 1); +} + +// Проверка наличия строки +while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + System.out.println(line); +} +``` + +--- + +### 5.5. Пример: сумма чисел +```java +import java.util.Scanner; + +public class SumExample { + public static void main(String[] args) { + Scanner scanner = new Scanner(System.in); + + System.out.print("Введите два числа: "); + int a = scanner.nextInt(); + int b = scanner.nextInt(); + + System.out.println("Сумма: " + (a + b)); + } +} +``` + +**Запуск:** +``` +Введите два числа: 10 20 +Сумма: 30 +``` + +--- + +### 5.6. Пример: чтение всех чисел и увеличение на 1 + +```java +Scanner scanner = new Scanner(System.in); + +// Читаем и увеличиваем пока есть числа +while (scanner.hasNextInt()) { + int number = scanner.nextInt(); + System.out.println(number + 1); +} +``` + +**Ввод:** +``` +1 2 3 +4 5 +``` + +**Вывод:** +``` +2 +3 +4 +5 +6 +``` + +--- + +### 5.7. Работа с пробелами и переводами строк + +Scanner **автоматически пропускает** пробельные символы (пробелы, табуляции, переводы строк): + +```java +Scanner scanner = new Scanner(System.in); + +// Ввод: " hello world " +String s1 = scanner.next(); // "hello" +String s2 = scanner.next(); // "world" +``` + +**Для чтения целых строк:** +```java +String line = scanner.nextLine(); // Читает ВСЁ до конца строки +``` + +--- + +### 5.8. Важные замечания + +1. **Scanner читает из потока последовательно** — прочитанные данные нельзя "вернуть обратно" +2. **Разделители по умолчанию** — пробелы, табуляции, переводы строк +3. **Можно настроить разделители** через метод `useDelimiter()` +4. **Нет прямого способа прочитать char** — используйте `scanner.next().charAt(0)` + +--- + +## 6. Домашнее задание + +### 6.1. Задание: Класс Reverse + +**Описание:** Разработать класс `Reverse`, который читает числа из стандартного ввода и выводит их в обратном порядке. + +**Требования:** +1. Числа могут быть на нескольких строках +2. В каждой строке может быть несколько чисел +3. Нужно развернуть **порядок строк** +4. Нужно развернуть **порядок чисел в каждой строке** +5. Числа разделены пробелами +6. Все числа помещаются в тип `int` +7. Использовать класс `Scanner` + +--- + +### 6.2. Примеры + +#### Пример 1 +**Ввод:** +``` +1 2 +3 +``` + +**Вывод:** +``` +3 +2 1 +``` + +--- + +#### Пример 2 +**Ввод:** +``` +3 +1 2 +``` + +**Вывод:** +``` +2 1 +3 +``` + +--- + +#### Пример 3: Отрицательные числа +**Ввод:** +``` +-1 -2 -3 +4 5 +``` + +**Вывод:** +``` +5 4 +-3 -2 -1 +``` + +--- + +#### Пример 4: Пустые строки +**Ввод:** +``` +1 2 + +3 4 +``` + +**Вывод:** +``` +4 3 + +2 1 +``` + +**Важно:** Пустые строки сохраняются! + +--- + +#### Пример 5: Множественные пробелы +**Ввод:** +``` +1 2 3 +4 5 +``` + +**Вывод:** +``` +5 4 +3 2 1 +``` + +**Важно:** В выводе между числами **ровно один пробел**! + +--- + +#### Пример 6: Полный пример +**Ввод:** +``` +1 2 +3 4 + +5 6 +``` + +**Вывод:** +``` +6 5 + +4 3 +2 1 +``` + +--- + +### 6.3. Требования к реализации + +#### Эффективность +- ✅ Программа должна работать **эффективно по памяти** +- ✅ Программа должна работать **эффективно по времени** +- ⚠️ Подумайте, как хранить данные оптимально + +#### Что использовать +- ✅ Класс `Scanner` для чтения +- ✅ Массивы для хранения данных +- ✅ Любые конструкции из стандартной библиотеки Java + +#### Формат вывода +- Числа в строке разделены **одним пробелом** +- Каждая строка заканчивается переводом строки +- Пустые строки выводятся как есть + +--- + +### 6.4. Алгоритм решения (подсказки) + +**Этапы:** +1. **Чтение данных** + - Читать строку за строкой + - Для каждой строки читать все числа + - Сохранять в структуру данных (двумерный массив?) + +2. **Разворот** + - Развернуть порядок строк + - В каждой строке развернуть порядок чисел + +3. **Вывод** + - Вывести каждую строку + - Числа через один пробел + +**Вопросы для размышления:** +- Как хранить строки разной длины? (Непрямоугольный массив!) +- Как отличить пустую строку от конца ввода? +- Как эффективно развернуть массивы? + +--- + +## Резюме лекции + +### Ключевые концепции + +1. **Массивы — ссылочный тип** + - Хранят ссылку, а не данные напрямую + - Передача в функции = передача ссылки + - Присваивание = копирование ссылки + +2. **Многомерные массивы = массивы массивов** + - Могут быть непрямоугольными + - Каждое измерение — отдельный массив + +3. **Значения по умолчанию** + - Примитивы: `0`, `false`, `'\0'` + - Ссылочные типы: `null` + +4. **Пакеты и импорты** + - `java.lang.*` импортируется автоматически + - Остальные пакеты требуют `import` + - Полное имя = `пакет.класс` + +5. **Полезные классы** + - `Arrays` — работа с массивами + - `Scanner` — чтение данных + - `System` — системные операции + +--- + +### Важные методы и техники + +```java +// Работа с массивами +Arrays.toString(array) // Красивый вывод +Arrays.fill(array, value) // Заполнение +System.arraycopy(...) // Копирование + +// Чтение данных +Scanner scanner = new Scanner(System.in); +scanner.nextInt() // Прочитать число +scanner.next() // Прочитать слово +scanner.nextLine() // Прочитать строку +scanner.hasNext() // Проверить наличие данных +``` + +--- + +### Типичные ошибки и их решения + +| Ошибка | Причина | Решение | +|--------|---------|---------| +| `NullPointerException` | Обращение к null | Проверять на null | +| `ArrayIndexOutOfBoundsException` | Выход за границы | Проверять индексы | +| `NegativeArraySizeException` | Отрицательный размер | Валидировать размер | + +--- + +### Что дальше + +- Практика работы с массивами +- Выполнение домашнего задания Reverse +- Следующая тема: классы и объекты + +--- + +**Полезные ссылки:** +- [Java Arrays Tutorial](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/arrays.html) +- [Java Arrays Documentation](https://docs.oracle.com/javase/specs/jls/se21/html/jls-10.html) +- [java.util.Arrays API](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/Arrays.html) +- [java.util.Scanner API](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/Scanner.html) + +--- + +**Удачи в выполнении домашнего задания! 🚀** + +# Конспект лекции 1: Введение в программирование (Java) +**Преподаватель:** Георгий Корнеев + +--- + +## 1. Организационные вопросы + +### 1.1. Контактная информация +- **Преподаватель:** Георгий Корнеев +- **Связь:** + - Основные вопросы → чат курса + - Сложные/долгие вопросы → email преподавателя + +### 1.2. Структура курса +**Последовательность предметов:** +- 1 курс: Введение в программирование (Java) +- 2 курс 1 семестр: Парадигмы программирования +- 2 курс 2 семестр: Технологии Java + +--- + +## 2. Организация учебного процесса + +### 2.1. Структура занятий + +#### Лекции +- Теоретический материал +- Live coding +- Демонстрации примеров + +#### Домашние задания +- Выдаются после каждой лекции +- Срок выполнения: около недели (чуть меньше, т.к. практики перед лекциями) +- Все последующие ДЗ (кроме первого) требуют самостоятельно написанного кода на Java + +#### Практические занятия +- Сдача **модификации** домашнего задания (не самого ДЗ!) +- Модификация тесно связана с домашним заданием +- **Пример:** ДЗ - скомпилировать Hello World, практика - скомпилировать другую программу + +--- + +## 3. Система оценивания + +### 3.1. Версии домашних заданий +- **Простая версия** → НЕ нужно делать +- **Сложная версия** → ОБЯЗАТЕЛЬНО делать + +### 3.2. Попытки сдачи +- **Количество попыток:** 3 (три вторника подряд) +- Если за 3 попытки не сдано → ДЗ не засчитывается + +### 3.3. Система задержек (delay) +**Фиксируется момент ПЕРВОЙ демонстрации рабочей программы преподавателю:** +- Сдача в первую неделю → delay = 0 +- Сдача во вторую неделю → delay = 1 +- Сдача в третью неделю → delay = 2 + +⚠️ **Важно:** Задержка определяется моментом первой демонстрации работающей программы, а не финальной сдачи! + +### 3.4. Система минусов +**Начисление минусов:** +- Преподаватель проверяет код +- За найденные проблемы назначаются минусы +- Минусы **накапливаются** при каждой попытке + +**Пример расчёта:** +- 1 попытка: 3 минуса +- 2 попытка: 2 минуса +- 3 попытка: 0 минусов (успешно) +- **Итого:** 5 минусов, delay = 0 (т.к. начали с первой недели) + +### 3.5. Бонусные баллы + +#### Бонусы за скорость сдачи +- **Условие:** Первый студент, сдавший ДЗ преподавателю в первую неделю +- **Количество:** До 5 человек (по числу преподавателей) + +#### Бонусы за найденные ошибки +- За обнаруженные ошибки и опечатки в материалах курса +- Сообщать в специальный раздел чата + +--- + +## 4. Зачёт + +### 4.1. Условия +- Проводится при неудовлетворительных баллах +- Если баллов достаточно → зачёт не обязателен + +### 4.2. Варианты сдачи +- **Досрочная сдача:** конец декабря +- **Стандартная сдача:** во время сессии + +--- + +## 5. Особенности первого домашнего задания + +### 5.1. Уникальные условия +- Несложное (т.к. не было лекций) +- **Срок сдачи:** только первая практика +- Больше не принимается + +--- + +## 6. Почему Java? + +### 6.1. Популярность +- **Индекс TIOBE:** Java в топе популярных языков +- В плотной группе с C, C++, Python +- Python вырвался вперёд в последние 2-3 года + +### 6.2. Преимущества Java + +#### Простота и безопасность +- Достаточно простой язык +- Сложно создать код, который: + - Убьёт компьютер + - Приведёт к непредсказуемому поведению + - Не воспроизводится на других машинах + +#### Содержательность +- Простой, но весьма содержательный язык +- Широкий спектр возможностей + +#### Области применения +1. **Серверные приложения** +2. **Desktop-приложения** +3. **Android-разработка** +4. **JVM-языки** (Kotlin, Scala и др.) + +--- + +## 7. Основы Java + +### 7.1. Концепция Java + +#### Виртуальная машина (JVM) +- Java компилируется не в машинный код, а в **байт-код** +- Байт-код исполняется на **виртуальной машине Java (JVM)** +- **Преимущество:** кроссплатформенность (Write Once, Run Anywhere) + +#### Процесс выполнения программы +1. Написание кода на Java (.java файлы) +2. Компиляция в байт-код (.class файлы) +3. Выполнение на JVM + +### 7.2. Установка и настройка + +#### Java Development Kit (JDK) +- **Что включает:** + - Компилятор Java + - JVM + - Стандартная библиотека + - Инструменты разработки + +#### Проверка установки +```bash +java -version # Проверка установленной версии JVM +javac -version # Проверка версии компилятора +``` + +### 7.3. Первая программа: Hello World + +#### Структура программы +```java +public class HelloWorld { + public static void main(String[] args) { + System.out.println("Hello, World!"); + } +} +``` + +#### Ключевые элементы +- `public class HelloWorld` - объявление публичного класса +- Имя класса должно совпадать с именем файла +- `public static void main(String[] args)` - точка входа в программу +- `System.out.println()` - вывод в консоль + +#### Компиляция и запуск +```bash +javac HelloWorld.java # Компиляция → создаёт HelloWorld.class +java HelloWorld # Запуск (БЕЗ расширения .class!) +``` + +--- + +## 8. Основы синтаксиса Java + +### 8.1. Типы данных + +#### Примитивные типы + +**Целочисленные:** +- `byte` - 8 бит (-128 до 127) +- `short` - 16 бит (-32768 до 32767) +- `int` - 32 бита (-2³¹ до 2³¹-1) +- `long` - 64 бита (-2⁶³ до 2⁶³-1) + +**Вещественные:** +- `float` - 32 бита (одинарная точность) +- `double` - 64 бита (двойная точность) + +**Другие:** +- `boolean` - true/false +- `char` - 16 бит (символ Unicode) + +#### Ссылочные типы +- Строки: `String` +- Массивы +- Объекты классов + +### 8.2. Переменные + +#### Объявление и инициализация +```java +int x; // Объявление +x = 10; // Инициализация +int y = 20; // Объявление с инициализацией +``` + +#### Константы +```java +final int MAX_VALUE = 100; // Константа (нельзя изменить) +``` + +### 8.3. Операторы + +#### Арифметические операторы +- `+` - сложение +- `-` - вычитание +- `*` - умножение +- `/` - деление +- `%` - остаток от деления + +#### Операторы сравнения +- `==` - равно +- `!=` - не равно +- `>`, `<` - больше, меньше +- `>=`, `<=` - больше или равно, меньше или равно + +#### Логические операторы +- `&&` - логическое И (AND) +- `||` - логическое ИЛИ (OR) +- `!` - логическое НЕ (NOT) + +#### Операторы присваивания +- `=` - присваивание +- `+=`, `-=`, `*=`, `/=` - составное присваивание + +### 8.4. Управляющие конструкции + +#### Условный оператор if +```java +if (условие) { + // код +} else if (другое_условие) { + // код +} else { + // код +} +``` + +#### Оператор switch +```java +switch (переменная) { + case значение1: + // код + break; + case значение2: + // код + break; + default: + // код по умолчанию +} +``` + +#### Цикл while +```java +while (условие) { + // код +} +``` + +#### Цикл do-while +```java +do { + // код +} while (условие); +``` + +#### Цикл for +```java +for (инициализация; условие; инкремент) { + // код +} + +// Пример: +for (int i = 0; i < 10; i++) { + System.out.println(i); +} +``` + +#### Цикл for-each +```java +for (тип элемент : коллекция) { + // код +} +``` + +--- + +## 9. Ввод и вывод + +### 9.1. Вывод в консоль +```java +System.out.println("текст"); // Вывод с переводом строки +System.out.print("текст"); // Вывод без перевода строки +``` + +### 9.2. Ввод из консоли +```java +import java.util.Scanner; + +Scanner scanner = new Scanner(System.in); +int number = scanner.nextInt(); // Чтение целого числа +String line = scanner.nextLine(); // Чтение строки +double d = scanner.nextDouble(); // Чтение вещественного числа +``` + +### 9.3. Работа с аргументами командной строки +```java +public static void main(String[] args) { + // args[0] - первый аргумент + // args[1] - второй аргумент + // args.length - количество аргументов +} +``` + +--- + +## 10. Массивы + +### 10.1. Одномерные массивы + +#### Объявление и создание +```java +int[] array; // Объявление +array = new int[10]; // Создание массива на 10 элементов +int[] array2 = new int[5]; // Объявление с созданием + +// Инициализация значениями +int[] array3 = {1, 2, 3, 4, 5}; +``` + +#### Обращение к элементам +```java +array[0] = 10; // Присваивание +int x = array[0]; // Чтение +``` + +#### Длина массива +```java +int length = array.length; // Поле length (НЕ метод!) +``` + +### 10.2. Многомерные массивы +```java +int[][] matrix = new int[3][4]; // 3 строки, 4 столбца +int[][] matrix2 = {{1,2}, {3,4}, {5,6}}; +``` + +--- + +## 11. Строки (String) + +### 11.1. Создание строк +```java +String s1 = "Hello"; +String s2 = new String("World"); +``` + +### 11.2. Основные методы +```java +int len = s.length(); // Длина строки +char ch = s.charAt(0); // Символ по индексу +String sub = s.substring(0, 5); // Подстрока +String upper = s.toUpperCase(); // В верхний регистр +String lower = s.toLowerCase(); // В нижний регистр +``` + +### 11.3. Конкатенация +```java +String result = s1 + s2; +String result2 = s1.concat(s2); +``` + +### 11.4. Особенности строк +- Строки в Java **неизменяемые** (immutable) +- Все операции создают новые строки +- Сравнение строк: использовать `equals()`, НЕ `==` + +```java +if (s1.equals(s2)) { // ПРАВИЛЬНО + // строки равны +} + +if (s1 == s2) { // НЕПРАВИЛЬНО (сравнивает ссылки) + // это не то, что вы думаете +} +``` + +--- + +## 12. Работа с тестами + +### 12.1. Репозиторий с тестами +- Тесты выкладываются в отдельный репозиторий +- Доступны исходный код и скомпилированная версия (.jar файлы) + +### 12.2. Запуск тестов + +#### Шаги для запуска +1. Скачать .jar файл с тестами +2. Скомпилировать свою программу +3. Проверить наличие .class файла +4. Запустить тесты командой + +#### Пример команды запуска +```bash +java -cp <путь_к_тестируемому_классу>:<путь_к_тест.jar> <название_теста> +``` + +**Рекомендация:** Создать скрипт для автоматизации запуска тестов + +### 12.3. Требования к решениям +- Для второго ДЗ: промежуточные результаты должны помещаться в `int` +- Использовать можно всё из стандартной поставки Java +- Неправильное использование → минусы при сдаче + +--- + +## 13. Дополнительное задание: Run.me + +### 13.1. Описание +- **Необязательное** задание для тех, кому "нечего делать" +- Содержит как простые, так и откровенно сложные задачи +- Предназначено для дополнительной практики + +### 13.2. Рекомендации +- ✅ Посмотреть полчасика +- ✅ Получить доступные плюсики +- ✅ Перейти к основному ДЗ +- ⚠️ НЕ закапываться в Run.me - это плохая стратегия! + +### 13.3. Оценивание +- Максимум баллов не больше, чем за обычное ДЗ +- Выполнить обычное ДЗ **гораздо полезнее** +- Сложность оценивается по количеству решивших +- Оценка: коэффициент × количество решённых задач + +### 13.4. Технические детали +- Можно использовать **всё что угодно** +- Проверяется только правильность URL результата +- Код НЕ проверяется (в отличие от обычных ДЗ) +- Каждая строка = отдельное число (не склеиваются) +- Не надо показывать код преподавателю + +--- + +## 14. Система баллов и оценок + +### 14.1. Начисление очков +- Очки начисляются за сдачу домашних заданий +- Видны в специальной таблице (НЕ в БАРСе) +- Несдача ДЗ = 0 очков + +### 14.2. Важные замечания +- ⚠️ Нельзя сравнивать очки за разные ДЗ +- ⚠️ Нельзя сравнивать очки между группами +- ✅ Гарантируется **локальная справедливость** внутри группы + +### 14.3. Локальная справедливость +**Правило:** +``` +Если студент A и студент B: +- Решили одинаковые ДЗ +- У A не больше задержки, чем у B +- У A не больше минусов, чем у B +→ То у A точно не меньше баллов, чем у B +``` + +### 14.4. Конвертация в БАРС +- Конвертация происходит **в конце курса** +- Очки нормируются относительно группы +- Чем больше очков относительно коллег → выше оценка + +### 14.5. Критерии оценок +- **Для 5 (отлично):** > 90 баллов в БАРСе +- За каждое ДЗ гарантируется минимальный процент баллов +- Сдать ДЗ **всегда** строго лучше, чем не сдать +- Разница между "сдано" и "не сдано" **существенная** + +### 14.6. Причины разброса баллов +- Результаты Run.me (различия в решённых задачах) +- Задержки при сдаче +- Количество минусов +- Относительный перформанс в группе + +### 14.7. Интерпретация статуса +- 🟢 **Зелёный** → всё хорошо +- 🔴 **Красный** → проблемы, нужно подтянуться + +--- + +## 15. Практические рекомендации + +### 15.1. Стратегия успешной сдачи +1. ✅ Начинать делать ДЗ сразу после выдачи +2. ✅ Стремиться сдать в первую неделю (delay = 0) +3. ✅ Тщательно проверять код перед демонстрацией +4. ✅ Использовать тесты для самопроверки +5. ✅ Создать скрипты для автоматизации + +### 15.2. Работа с кодом +- Можно использовать линтеры для Java +- Следить за качеством кода +- Исправлять замечания преподавателя +- Помнить о накоплении минусов + +### 15.3. Что НЕ стоит делать +- ❌ Откладывать ДЗ на последнюю неделю +- ❌ Закапываться в Run.me вместо основного ДЗ +- ❌ Игнорировать замечания преподавателя +- ❌ Сравнивать свои баллы с другими группами + +--- + +## 16. Обратная связь + +### 16.1. Если недоволен результатом +- Использовать кнопку 👎 (thumbs down) под ответами +- Написать в чат курса +- Отправить email преподавателю + +### 16.2. Если что-то непонятно +- Задавать вопросы в чате +- Подходить к преподавателю после лекции +- Писать на email для сложных вопросов + +--- + +## Резюме первой лекции + +### Ключевые выводы +1. **Организация:** Лекции → ДЗ → Практика (сдача модификаций) +2. **Оценивание:** Очки → конвертация в БАРС → итоговая оценка +3. **Java:** Популярный, простой, безопасный, кроссплатформенный язык +4. **Стратегия:** Сдавать быстро, качественно, регулярно + +### Что делать дальше +1. Настроить среду разработки (JDK установлен) +2. Начать второе домашнее задание +3. Скачать и настроить тесты +4. Стремиться сдать в первую неделю + +### Важные даты +- Сдача ДЗ: каждый вторник (3 попытки) +- Досрочный зачёт: конец декабря +- Стандартный зачёт: сессия + +--- + +## Полезные ссылки +- Чат курса (основные вопросы) +- Email преподавателя (сложные вопросы) +- Репозиторий с тестами +- Материалы курса +- Раздел "Ошибки и опечатки" в чате + +--- + +**Успехов в изучении Java! 🚀** diff --git a/test-rules.md b/test-rules.md new file mode 100644 index 0000000..6e0fdf5 --- /dev/null +++ b/test-rules.md @@ -0,0 +1,113 @@ +--- +gitea: none +include_toc: true +--- + +# Зачёт по дисциплине «Введение в программирование» + +## Расписание + +Досрочная сдача зачёта: + + * M3132-35: 30 декабря с 11:30, ауд. 2137 + * M3136-39: 30 декабря с 11:30, ауд. 2137 + +Сдача зачёта в сессию: + +* M3132-35: 10 января с 11:30, ауд. 1229 +* M3136-39: 20 января с 11:30, ауд. 2137 + + +Пересдача зачёта пройдёт: + +* M3132-39: TBA +* M3136-39: TBA + + +## Сдача зачёта + +Если вас устраивают ваши баллы, то зачёт можно не сдавать. +Вы можете сдавать зачёт либо досрочно, либо во время сессии, по вашему выбору. + +Для того, чтобы записаться на сдачу зачёта надо заполнить +[форму](https://docs.google.com/forms/d/e/1FAIpQLScjft8hZCjlfoeVicSJHnX_uMW7xpA5RxSMQwqhy6aXGZWCGw/viewform). +Для заполнения вам потребуются логин и пароль от репозитория. +Если не указано обратного, то на сдачу надо записаться до 9:00 дня сдачи. +Если вы не записались на сдачу вовремя, то вы можете прийти на зачёт, +но вы будете обработаны в ручном режиме после решения проблем всех записавшихся вовремя. +Время на обработку компенсировано не будет. + +Если у вас больше 59 баллов, то их округлят до 60 если вы заполните форму +и пообещаете не сдавать зачёт (это требуется отметить в форме). + + +## Формат заданий + +Вам будет выдано задание, связанное с пройденными темами. +В условии описано, что вам надо сделать и набор требований, +которым должно удовлетворять решение (примерно как в домашних заданиях). + +Некоторые задания рассчитаны на то, что вы адаптируте или скомпануете +код одного или нескольких ДЗ. +Если вы не сдали соответствующие ДЗ, то вы можете написать весь код с нуля, +но это будет сложнее. + +Если вам что-то не ясно в задании (например, оно кажется подозрительно простым), +то вы можете задать вопрос по условию. +Принимаются только вопросы о том, что надо сделать. +Как именно делать остаётся на ваше усмотрение. + +По решению должно быть очевидно как его запускать. +Например, назвать основной класс `Main` или по имени задания — +хорошая идея, а `FooBazFactoryProvider` — нет. + +Если вы хотете передать проверяющему какую-то дополнительную информацию +(например, о выбранном компромиссе между памятью и временем исполнения), +то напишите её либо в комментариях к коду, либо положите рядом с кодом +`README.md`. + +В решении вы можете использовать ваш код из ДЗ и код, +написанный преподавателями на лекциях. + + +## Порядок сдачи + + 1. Заранее проверьте, что у вас работает всё, необходимое для сдачи. + Претензии вида «у меня не работал компилятор/IDE/git/браузер/интернет» не принимаются. + 1. За 10 минут до начала зачёта сбор студентов в соответствующей аудитории. + Позаботьтесь, что бы вам хватило заряда ноутбука или принесите с собой удлинитель-тройник. + 1. Организационные вопросы. + В том числе, можно отказаться от участия, если записались по ошибке, + или вам больше не актуально. + 1. В момент *T* объявляется начало и выдаются билеты с заданиями. + 1. Ответы на вопросы по билетам до *T*+30 минут. + 1. Вы пишите решение и записываете его в каталог `java-solutions` зачётного репозитория + `https://www.kgeorgiy.info/git-students/year2025//prog-intro-exam`. + 1. В момент времени *T*+3 часа фиксируется состояние репозиториев. + 1. Проверяется код на состояние *T*+3 часа. Это может занять несколько дней. + 1. Результаты проверки отображаются в табличке, + комментарии по проверке загружаются в репозитории. + + +## Система оценки + +В репозитории есть код, решающий поставленную задачу (возможно не всю): +`20` баллов минус баллы за проблемы: + + * `#` — большая проблема (обычно не выполнено одно из требований задания): `−5` баллов + * `*` — средняя проблема (обычно нарушение неоднократно обсуждавшихся рекомендаций, + например, утечка ресурсов, если это не является основной целью задания): `−2` балла + * `-` — маленькая проблема (например, однократное нарушение правил оформления кода): `−1` балл + +Код (почти) отсутствует/не имеет отношения к заданию, преподаватель не нашёл ваш код: `−5` баллов + +Код не компилируется/синтаксически некорректен: `−10` баллов, дальнейшая проверка не осуществляется. + +Код списан: `−20` баллов + + +## Полезные ссылки + + 1. [Форма для записи на зачет](https://docs.google.com/forms/d/e/1FAIpQLScjft8hZCjlfoeVicSJHnX_uMW7xpA5RxSMQwqhy6aXGZWCGw/viewform) + 1. [Результаты записи на зачет](https://docs.google.com/spreadsheets/d/1g1XA_62KxWQHjXHsGoEAg0nisYQGZ_amLPVWXH_ftEA/edit?gid=1533773578#gid=1533773578) + 1. Экзаменационный репозиторий: `https://www.kgeorgiy.info/git-students/year2025//prog-intro-exam`